Init
This commit is contained in:
23 files changed
+1216
No files matched your search
@@ -0,0 +1,142 @@
|
||||
# Bad IP Blocker for Laravel
|
||||
|
||||
Block requests from bad IP addresses using a local JSON list, and automatically block clients that
|
||||
probe for paths no normal visitor requests (`/.well-known/security.txt`, `/.env`, `/wp-login.php`, …).
|
||||
|
||||
## Features
|
||||
|
||||
- Local JSON list – no external service, no database
|
||||
- Automatic blocking of scanners (only when the requested path returns 404 in your app)
|
||||
- Artisan commands and a facade to manage the list
|
||||
- Optional expiry (TTL) per block
|
||||
- Whitelist with CIDR support (private and loopback ranges by default)
|
||||
- Laravel 11, 12 and 13
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
composer require mattitja/bad-ip-blocker
|
||||
```
|
||||
|
||||
Publish the config (optional):
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --tag=bad-ip-blocker-config
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
That's it – after `composer require` the middleware is registered globally and starts blocking.
|
||||
Expired blocks are pruned daily via the Laravel scheduler (make sure `schedule:run` is running).
|
||||
|
||||
If you'd rather register the middleware yourself, set `BAD_IP_BLOCKER_AUTO_REGISTER=false` and add it in
|
||||
`bootstrap/app.php`:
|
||||
|
||||
```php
|
||||
use Mattitja\BadIpBlocker\Middleware\CheckBadIps;
|
||||
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
$middleware->append(CheckBadIps::class);
|
||||
})
|
||||
```
|
||||
|
||||
It must run globally (not only in a route group) so it also sees requests for routes that don't exist.
|
||||
Registering it both ways is harmless – it only runs once.
|
||||
|
||||
### Behind a proxy or load balancer – important
|
||||
|
||||
This is the one thing you may need to configure. The middleware uses `$request->ip()`. Behind a reverse
|
||||
proxy, load balancer or Cloudflare this returns the proxy's address unless you trust the proxy. Configure it
|
||||
so the real client IP from `X-Forwarded-For` is used:
|
||||
|
||||
```php
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
$middleware->trustProxies(at: ['10.0.0.0/8']); // your proxy addresses
|
||||
})
|
||||
```
|
||||
|
||||
Private ranges are whitelisted by default, so a missing proxy config never blocks every visitor. But until
|
||||
you configure trusted proxies, **nothing is blocked** behind the proxy either – it fails silently.
|
||||
|
||||
## Managing the list
|
||||
|
||||
### Artisan
|
||||
|
||||
```bash
|
||||
php artisan bad-ip-blocker:add 203.0.113.10 --reason="Scraper" --ttl=86400
|
||||
php artisan bad-ip-blocker:remove 203.0.113.10
|
||||
php artisan bad-ip-blocker:list
|
||||
php artisan bad-ip-blocker:prune # remove expired blocks
|
||||
php artisan bad-ip-blocker:clear --force # remove all blocks
|
||||
```
|
||||
|
||||
### Facade
|
||||
|
||||
```php
|
||||
use Mattitja\BadIpBlocker\Facades\BadIpBlocker;
|
||||
|
||||
BadIpBlocker::block('203.0.113.10', 'Scraper', ttl: 3600); // ttl in seconds, null = permanent
|
||||
BadIpBlocker::unblock('203.0.113.10');
|
||||
BadIpBlocker::isBlocked('203.0.113.10');
|
||||
BadIpBlocker::all();
|
||||
```
|
||||
|
||||
### Pruning
|
||||
|
||||
Expired blocks are ignored automatically and removed from the file by a daily scheduled
|
||||
`bad-ip-blocker:prune`. Disable it with `BAD_IP_BLOCKER_SCHEDULE_PRUNE=false`.
|
||||
|
||||
## Automatic blocking
|
||||
|
||||
When a request matches a pattern in `auto_block.paths` **and your app responds with 404**, the IP is blocked.
|
||||
Routes that exist in your app are never affected – if you publish your own `security.txt`, reading it is fine.
|
||||
|
||||
> If your app has a `Route::fallback()` that returns 200 (e.g. an SPA), matching requests never return 404,
|
||||
> so automatic blocking won't trigger for them.
|
||||
|
||||
Default patterns include `.well-known/security.txt`, `security.txt`, `.env*`, `.git/*`, `wp-login.php`,
|
||||
`wp-admin*`, `xmlrpc.php`, `phpmyadmin*`, `vendor/phpunit/*` and `*.php`. Ordinary 404s
|
||||
(e.g. `/missing-page`) never trigger a block.
|
||||
|
||||
Tune it in `config/bad-ip-blocker.php`:
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `auto_block.enabled` | `true` | Env `BAD_IP_BLOCKER_AUTO_BLOCK` |
|
||||
| `auto_block.paths` | see above | `Request::is()` patterns |
|
||||
| `auto_block.max_hits` | `1` | Matching 404s before blocking |
|
||||
| `auto_block.decay` | `3600` | Seconds a hit counter is kept |
|
||||
| `auto_block.ttl` | `604800` (7 days) | Env `BAD_IP_BLOCKER_AUTO_BLOCK_TTL`, `null` = permanent |
|
||||
|
||||
## Configuration
|
||||
|
||||
| Key | Default |
|
||||
| --- | --- |
|
||||
| `enabled` | `true` (env `BAD_IP_BLOCKER_ENABLED`) |
|
||||
| `auto_register` | `true` (env `BAD_IP_BLOCKER_AUTO_REGISTER`) |
|
||||
| `schedule_prune` | `true` (env `BAD_IP_BLOCKER_SCHEDULE_PRUNE`) |
|
||||
| `storage_path` | `storage/app/bad-ip-blocker.json` (env `BAD_IP_BLOCKER_STORAGE_PATH`) |
|
||||
| `response.status` / `response.message` | `418` / `Blocked.` |
|
||||
| `whitelist` | loopback + private ranges (IPs or CIDR) |
|
||||
|
||||
The list is a plain JSON file per server. If you run several servers or containers, point `storage_path` at
|
||||
persistent, shared storage – otherwise each instance has its own list and a container's list is lost on redeploy.
|
||||
|
||||
## Example response when blocked
|
||||
|
||||
```
|
||||
HTTP/1.1 418 I'm a teapot
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
|
||||
Blocked.
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
composer test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT License © Mattitja AB
|
||||
Reference in new issue
Block a user