Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/advanced/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Checks blocklist rules. On the first match, dispatches `BlocklistMatched`, sets

For each fail2ban rule:

1. Checks if the key is already banned - if so, returns a blocked result immediately
1. Checks if the key is already banned - if so, dispatches `Fail2BanBlocked` and returns a blocked result immediately
2. If the filter matches, increments the failure counter and blocks the request (`403`). A match below the threshold sets `DecisionPath::Fail2BanMatched` and dispatches `Fail2BanMatched`; the Nth match additionally bans the key, sets `DecisionPath::Fail2BanBanned`, and dispatches `Fail2BanBanned` (never both events)

The pre-handler path (during `decide()`) blocks on every match and bans at the threshold. The post-handler path (via `processRecordedSignal()`) shares the same `count >= threshold` ban comparison but never blocks the current request and never dispatches `Fail2BanMatched`. The ban fires on the Nth match, consistent with Allow2Ban.
Expand All @@ -114,7 +114,7 @@ For each throttle rule:

Unlike other evaluators, Allow2BanEvaluator **processes all rules before returning**. For each allow2ban rule:

1. If the key is already banned, records the block (regardless of the filter)
1. If the key is already banned, records the block (regardless of the filter); the rule that captures the block dispatches `Allow2BanBlocked`
2. Otherwise, if the rule has a filter that does not match, skips the rule (not counted)
3. Otherwise, increments the counter and bans if the threshold is reached (`count >= threshold`); the matching request itself passes until it is the one that reaches the threshold

Expand Down
30 changes: 29 additions & 1 deletion docs/advanced/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ All events are dispatched **synchronously** during request processing. Every eve
| `ThrottleExceeded` | Request exceeds a throttle limit | `rule`, `key`, `limit`, `period`, `count`, `retryAfter`, `serverRequest` |
| `Fail2BanMatched` | Fail2Ban filter matches and blocks a request below the ban threshold | `rule`, `key`, `threshold`, `period`, `count`, `serverRequest` |
| `Fail2BanBanned` | Key banned after reaching the failure threshold | `rule`, `key`, `threshold`, `period`, `banSeconds`, `count`, `serverRequest` |
| `Fail2BanBlocked` | Request blocked because its key is already banned by Fail2Ban | `rule`, `key`, `serverRequest` |
| `Allow2BanBanned` | Key banned after exceeding request threshold | `rule`, `key`, `threshold`, `period`, `banSeconds`, `count`, `serverRequest` |
| `Allow2BanBlocked` | Request blocked because its key is already banned by Allow2Ban | `rule`, `key`, `serverRequest` |
| `TrackHit` | Tracking rule filter matches | `rule`, `key`, `period`, `count`, `limit`, `thresholdReached`, `serverRequest` |
| `FirewallError` | Error in fail-open mode (cache failure, etc.) | `exception`, `serverRequest` |
| `PerformanceMeasured` | After every firewall decision | `decisionPath`, `durationMicros`, `ruleName` |
Expand Down Expand Up @@ -110,6 +112,18 @@ $event->count; // int - Failure count that triggered the ban
$event->serverRequest; // ServerRequestInterface
```

### Fail2BanBlocked

Dispatched when a request is blocked because its key is already banned by a Fail2Ban rule. The filter is not evaluated for banned keys, and the event fires on **every** blocked request, so a hammering client produces one event per request; aggregate in high-volume listeners.

```php
use Flowd\Phirewall\Events\Fail2BanBlocked;

$event->rule; // string - Rule name
$event->key; // string - Banned key (e.g., IP address)
$event->serverRequest; // ServerRequestInterface
```

### Allow2BanBanned

Dispatched when an Allow2Ban rule bans a key after the counted-request threshold is reached. Allow2Ban counts every request for a key, or only the requests an optional filter matches, letting matching requests pass until the threshold.
Expand All @@ -126,6 +140,18 @@ $event->count; // int - Request count that triggered the ban
$event->serverRequest; // ServerRequestInterface
```

### Allow2BanBlocked

Dispatched when a request is blocked because its key is already banned by an Allow2Ban rule. Banned keys block every request regardless of the rule's filter, and the event fires on **every** blocked request; aggregate in high-volume listeners.

```php
use Flowd\Phirewall\Events\Allow2BanBlocked;

$event->rule; // string - Rule name
$event->key; // string - Banned key (e.g., IP address)
$event->serverRequest; // ServerRequestInterface
```

### TrackHit

Track events fire on **every** matching request; they never block. When a `limit` is configured on the track rule, the `thresholdReached` flag becomes `true` once the counter reaches the threshold. This makes track rules ideal for alerting without blocking.
Expand Down Expand Up @@ -243,6 +269,7 @@ Returns an array organized by category, each with a total and a per-rule breakdo
'fail2ban_matched' => ['total' => 4, 'by_rule' => ['scanner-probe' => 4]],
'fail2ban_banned' => ['total' => 1, 'by_rule' => ['scanner-probe' => 1]],
'allow2ban_banned' => ['total' => 2, 'by_rule' => ['high-volume' => 2]],
'allow2ban_blocked' => ['total' => 1, 'by_rule' => ['high-volume' => 1]],
'track_hit' => ['total' => 50, 'by_rule' => ['api-calls' => 50]],
'passed' => ['total' => 1000, 'by_rule' => []],
]
Expand All @@ -259,11 +286,12 @@ Returns an array organized by category, each with a total and a per-rule breakdo
| `fail2ban_banned` | `Fail2BanBanned` | New Fail2Ban bans issued |
| `fail2ban_blocked` | `PerformanceMeasured` | Requests blocked by existing Fail2Ban bans |
| `allow2ban_banned` | `Allow2BanBanned` | New Allow2Ban bans issued |
| `allow2ban_blocked` | `PerformanceMeasured` | Requests blocked by existing Allow2Ban bans |
| `track_hit` | `TrackHit` | Tracking rule matches |
| `passed` | `PerformanceMeasured` | Requests that passed all checks |

::: tip
The `passed` and `fail2ban_blocked` categories are derived from the `PerformanceMeasured` event, which fires on every request. All other categories come from their dedicated events.
The `passed`, `fail2ban_blocked` and `allow2ban_blocked` categories are derived from the `PerformanceMeasured` event, which fires on every request. The dedicated `Fail2BanBlocked` and `Allow2BanBlocked` events are intentionally not counted so blocked requests are not double-counted. All other categories come from their dedicated events.
:::

### Resetting Counters
Expand Down
2 changes: 2 additions & 0 deletions docs/advanced/track-notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,9 @@ Phirewall dispatches events for every significant decision. You can listen for a
| `ThrottleExceeded` | A rate limit is exceeded | `rule`, `key`, `limit`, `period`, `count`, `retryAfter`, `serverRequest` |
| `Fail2BanMatched` | A Fail2Ban filter match is blocked below the threshold | `rule`, `key`, `threshold`, `period`, `count`, `serverRequest` |
| `Fail2BanBanned` | A client is banned by Fail2Ban | `rule`, `key`, `threshold`, `period`, `banSeconds`, `count`, `serverRequest` |
| `Fail2BanBlocked` | A request from a key already banned by Fail2Ban is blocked | `rule`, `key`, `serverRequest` |
| `Allow2BanBanned` | A client is banned by Allow2Ban | `rule`, `key`, `threshold`, `period`, `banSeconds`, `count`, `serverRequest` |
| `Allow2BanBlocked` | A request from a key already banned by Allow2Ban is blocked | `rule`, `key`, `serverRequest` |
| `PerformanceMeasured` | Every firewall decision (for metrics) | `decisionPath`, `durationMicros`, `ruleName` |
| `FirewallError` | An exception occurs in fail-open mode | `exception`, `serverRequest` |

Expand Down
36 changes: 31 additions & 5 deletions docs/features/fail2ban.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Before 0.8 a filter match below the threshold passed through, so a Fail2Ban filt
### How It Works

```text
Request --> Is key already banned? --> Yes --> 403 Forbidden
Request --> Is key already banned? --> Yes --> 403 Forbidden (Fail2BanBlocked event)
|
No
|
Expand All @@ -42,7 +42,7 @@ Request --> Is key already banned? --> Yes --> 403 Forbidden
2. Every match is **blocked with `403`** and counted per **key** (e.g., IP address) within a time **period**
3. A match **below** the threshold blocks via `DecisionPath::Fail2BanMatched` and dispatches the [`Fail2BanMatched`](#fail2banmatched) event
4. When the count **reaches** the **threshold**, the key is additionally **banned** for the configured duration; that match blocks via `DecisionPath::Fail2BanBanned` and dispatches [`Fail2BanBanned`](#fail2banbanned) (never both events)
5. Banned keys then receive `403 Forbidden` immediately, without further rule evaluation
5. Banned keys then receive `403 Forbidden` immediately, without further rule evaluation; each of these blocks dispatches [`Fail2BanBlocked`](#fail2banblocked)

### Configuration

Expand Down Expand Up @@ -309,7 +309,7 @@ Either way, an already-banned key is blocked on **every** request regardless of
### How It Works

```text
Request --> Is key already banned? --> Yes --> 403 Forbidden
Request --> Is key already banned? --> Yes --> 403 Forbidden (Allow2BanBlocked event)
|
No
|
Expand Down Expand Up @@ -442,7 +442,7 @@ $config->allow2ban->add(
| **On a match** | **Blocks immediately** (`403`) and counts | **Lets the request pass** and counts, until the threshold |
| **Trigger** | Any match (block); Nth match (ban) | Nth counted request (ban) |
| **Use case** | Unambiguously malicious matches (scanner paths, invalid signatures), signal-only rules | Login brute-force counting, volume abuse, "count these, ban after N" |
| **Events** | `Fail2BanMatched` (sub-threshold block), `Fail2BanBanned` (ban) | `Allow2BanBanned` (ban) |
| **Events** | `Fail2BanMatched` (sub-threshold block), `Fail2BanBanned` (ban), `Fail2BanBlocked` (banned-key block) | `Allow2BanBanned` (ban), `Allow2BanBlocked` (banned-key block) |
| **Ban parameter** | `$ban` | `$banSeconds` |

## Managing Bans
Expand Down Expand Up @@ -487,7 +487,7 @@ Notes:

## Events

Fail2Ban and Allow2Ban dispatch events through your PSR-14 event dispatcher. Fail2Ban dispatches `Fail2BanMatched` for every match blocked below the threshold and `Fail2BanBanned` for the match that bans (never both for the same request); Allow2Ban dispatches `Allow2BanBanned` when a key is banned.
Fail2Ban and Allow2Ban dispatch events through your PSR-14 event dispatcher. Fail2Ban dispatches `Fail2BanMatched` for every match blocked below the threshold and `Fail2BanBanned` for the match that bans (never both for the same request); Allow2Ban dispatches `Allow2BanBanned` when a key is banned. A request blocked because its key is **already banned** dispatches `Fail2BanBlocked` or `Allow2BanBlocked`.

### Fail2BanMatched

Expand Down Expand Up @@ -520,6 +520,19 @@ $event->count; // int - Failure count that triggered the ban
$event->serverRequest; // ServerRequestInterface
```

### Fail2BanBlocked

Dispatched when a request is blocked because its key is already banned. The filter is not evaluated for banned keys, and the event fires on **every** blocked request, so a hammering client produces one event per request.

```php
use Flowd\Phirewall\Events\Fail2BanBlocked;

// Event properties
$event->rule; // string - Rule name
$event->key; // string - Banned key (e.g., IP address)
$event->serverRequest; // ServerRequestInterface
```

### Allow2BanBanned

```php
Expand All @@ -535,6 +548,19 @@ $event->count; // int - Request count that triggered the ban
$event->serverRequest; // ServerRequestInterface
```

### Allow2BanBlocked

Dispatched when a request is blocked because its key is already banned. Banned keys block every request regardless of the rule's filter, and the event fires on **every** blocked request.

```php
use Flowd\Phirewall\Events\Allow2BanBlocked;

// Event properties
$event->rule; // string - Rule name
$event->key; // string - Banned key
$event->serverRequest; // ServerRequestInterface
```

### Alerting on Bans

```php
Expand Down
Loading