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
10 changes: 5 additions & 5 deletions docs/advanced/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ The `EvaluationContext` is a mutable transport object that carries shared config
| `normalize` | `Closure(string): string` | Discriminator key normalizer |
| `responseHeadersEnabled` | `bool` | Whether `X-Phirewall` headers are active |
| `rateLimitHeadersEnabled` | `bool` | Whether `X-RateLimit-*` headers are active |
| `owaspDiagnosticsHeaderEnabled` | `bool` | Whether `X-Phirewall-Owasp-Rule` header is active |
| `counter` | `FixedWindowCounter` | Shared counter for fail2ban, allow2ban, and track rules |
| `diagnosticsHeadersEnabled` | `bool` | Whether matcher-provided diagnostic headers are copied onto blocked responses |
| `counter` | `FixedWindowCounter` | Shared counter for Fail2Ban, Allow2Ban, and track rules |
| `decisionPath` | `DecisionPath` | Updated by evaluators to record which stage decided |
| `decisionRule` | `?string` | Updated by evaluators to record the matching rule name |
| `pendingRateLimitHeaders` | `?array` | Rate-limit headers captured by `ThrottleEvaluator` for pass-through responses |
Expand All @@ -89,11 +89,11 @@ Checks safelist rules. On the first match, dispatches `SafelistMatched`, sets th

### BlocklistEvaluator

Checks blocklist rules. On the first match, dispatches `BlocklistMatched`, sets the decision path to `Blocklisted`, and returns `FirewallResult::blocked()`. For OWASP-sourced rules, includes the `X-Phirewall-Owasp-Rule` diagnostic header when enabled.
Checks blocklist rules. On the first match, dispatches `BlocklistMatched`, sets the decision path to `Blocklisted`, and returns `FirewallResult::blocked()`. Diagnostic headers the matcher declared in its `MatchResult` metadata (`diagnostic_headers`) are copied onto the response when `enableDiagnosticsHeaders()` is active.

### Fail2BanEvaluator

For each fail2ban rule:
For each Fail2Ban rule:

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)
Expand All @@ -112,7 +112,7 @@ For each throttle rule:

### Allow2BanEvaluator

Unlike other evaluators, Allow2BanEvaluator **processes all rules before returning**. For each allow2ban 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); 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)
Expand Down
2 changes: 2 additions & 0 deletions docs/advanced/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ All events are dispatched **synchronously** during request processing. Every eve

All event classes live in the `Flowd\Phirewall\Events` namespace and are `readonly`.

Every matcher-carried event (`SafelistMatched`, `BlocklistMatched`, `ThrottleExceeded`, `TrackHit`, `Fail2BanMatched`, `Fail2BanBanned`, `Allow2BanBanned`) additionally exposes a `MatchResult $matchResult` property with the match (or filter/scope match) that triggered it. It is nullable only on `ThrottleExceeded`, `Fail2BanBanned` and `Allow2BanBanned`, where no matcher may have run (unscoped throttles, post-handler `recordFailure()`/`recordHit()` signals, filterless Allow2Ban rules). Listeners can read matcher metadata such as `diagnostic_headers` from it without enabling the attacker-visible response headers.

### SafelistMatched

Dispatched when a request matches a safelist rule. The request bypasses all remaining checks.
Expand Down
6 changes: 3 additions & 3 deletions docs/advanced/portable-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Everything `PortableConfig` can express today.
| Factory | Matches when … |
|---------|----------------|
| `filterAll()` | always |
| `filterNone()` | never: a filter that never matches; use it for a rule that must not be assertable from any request property (e.g. a fail2ban driven solely by `RequestContext::recordFailure`) |
| `filterNone()` | never: a filter that never matches; use it for a rule that must not be assertable from any request property (e.g. a Fail2Ban rule driven solely by `RequestContext::recordFailure`) |
| `filterPathEquals(path)` | the path equals `path` |
| `filterPathPrefix(prefix)` | the path starts with `prefix` |
| `filterPathRegex(pattern)` | the path matches the PCRE `pattern` (delimiters included) |
Expand All @@ -88,7 +88,7 @@ Everything `PortableConfig` can express today.
`filterIp`, `filterKnownScanners`, and `filterSuspiciousHeaders` compile to the dedicated matcher classes (so you get their diagnostics and CIDR handling); the remaining filters compile to a request-predicate closure.

::: warning
`filterHeaderEquals`, `filterHeaderPresent`, and `filterHeaderRegex` are rejected on `safelist()` (and on `fromArray()` deserialize): a client-controlled header value would be a forgeable bypass token (anyone presenting it skips every downstream rule). They remain valid on blocklists, throttles, fail2ban, allow2ban, and track rules.
`filterHeaderEquals`, `filterHeaderPresent`, and `filterHeaderRegex` are rejected on `safelist()` (and on `fromArray()` deserialize): a client-controlled header value would be a forgeable bypass token (anyone presenting it skips every downstream rule). They remain valid on blocklists, throttles, Fail2Ban, Allow2Ban, and track rules.
:::

### Key extractors
Expand Down Expand Up @@ -132,7 +132,7 @@ Pattern backends carry a list of entries; each entry has a `PatternKind`:
|---------|------------------------------|
| `enableRateLimitHeaders()` | emit `X-RateLimit-*` headers |
| `enableResponseHeaders()` | emit `X-Phirewall-*` headers |
| `enableOwaspDiagnosticsHeader()` | emit the OWASP diagnostics header |
| `enableDiagnosticsHeaders()` | copy matcher-provided diagnostic headers onto blocked responses (`enableOwaspDiagnosticsHeader()` is a deprecated alias) |
| `setFailOpen(bool)` | fail-open (default) vs fail-closed on backend errors |
| `setKeyPrefix(prefix)` | cache-key prefix |

Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/track-notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ Phirewall dispatches events for every significant decision. You can listen for a
| `PerformanceMeasured` | Every firewall decision (for metrics) | `decisionPath`, `durationMicros`, `ruleName` |
| `FirewallError` | An exception occurs in fail-open mode | `exception`, `serverRequest` |

All event classes live in the `Flowd\Phirewall\Events` namespace and are `readonly`.
All event classes live in the `Flowd\Phirewall\Events` namespace and are `readonly`. The matcher-carried events also expose a `MatchResult $matchResult` with the match that triggered them; it is nullable only where no matcher may have run (see [Observability](/advanced/observability#event-summary)).

## Notification Examples

Expand Down
12 changes: 6 additions & 6 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ $context?->recordFailure('login-failures');

The second argument to `recordFailure()` is optional; when omitted, the firewall extracts the discriminator key from the rule's own `keyExtractor`. The matching Fail2Ban rule should use `filter: fn($request): bool => false` so it only counts failures signaled programmatically.

For allow2ban rules, use `$context->recordHit('rule-name')`, same shape, routed through the allow2ban evaluator instead.
For Allow2Ban rules, use `$context->recordHit('rule-name')`, same shape, routed through the Allow2Ban evaluator instead.

## Rate Limiting

Expand Down Expand Up @@ -322,13 +322,13 @@ $crs->enable(942100); // Re-enable it later

### How do I debug which OWASP rule is blocking a request?

Enable the diagnostics header:
Enable diagnostic headers:

```php
$config->enableOwaspDiagnosticsHeader();
$config->enableDiagnosticsHeaders();
```

This adds an `X-Phirewall-Owasp-Rule` header to blocked responses containing the matched rule ID.
The CRS matcher attaches an `X-Phirewall-Owasp-Rule` header with the matched rule ID to blocked responses. This works wherever the matcher decides the block: as a blocklist rule and as a Fail2Ban filter. Any matcher can ship such headers via the `diagnostic_headers` metadata key on its `MatchResult`.

::: warning
Only enable this in development or staging. In production, it reveals information about your security rules to potential attackers.
Expand Down Expand Up @@ -372,7 +372,7 @@ Common causes:
Debug steps:
1. Enable `$config->enableResponseHeaders()` and check the `X-Phirewall` and `X-Phirewall-Matched` response headers to identify the blocking rule
2. Temporarily disable suspect rules and re-enable them one by one
3. If OWASP rules are involved, enable `$config->enableOwaspDiagnosticsHeader()` to see which rule ID matched
3. If OWASP rules are involved, enable `$config->enableDiagnosticsHeaders()` to see which rule ID matched

### Rate limits are not working in PHP-FPM

Expand All @@ -386,7 +386,7 @@ Enable `$config->enableResponseHeaders()` and check the response headers on bloc
|--------|-------|
| `X-Phirewall` | Block type: `blocklist`, `throttle`, `fail2ban`, or `allow2ban` |
| `X-Phirewall-Matched` | Name of the rule that triggered the block |
| `X-Phirewall-Owasp-Rule` | OWASP rule ID (only if diagnostics are enabled) |
| `X-Phirewall-Owasp-Rule` | OWASP rule ID (only if `enableDiagnosticsHeaders()` is active) |

::: info
These headers are disabled by default. Call `$config->enableResponseHeaders()` to enable them for debugging.
Expand Down
10 changes: 5 additions & 5 deletions docs/features/owasp-crs.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ $config = new Config(new InMemoryCache());
$config = $config->with(Presets::blocklist(ParanoiaLevel::Level1));
```

Want to also ban repeat offenders? Use the fail2ban preset instead. A CRS
Want to also ban repeat offenders? Use the Fail2Ban preset instead. A CRS
match is malicious by definition, so from 0.8 both presets block every match
with `403`; the difference is that the fail2ban preset additionally **bans**
with `403`; the difference is that the Fail2Ban preset additionally **bans**
the key after the threshold. A banned attacker is then blocked by a cheap ban
lookup (the CRS engine no longer runs for them), and the ban is observable via
`Fail2BanBanned` and mirrorable to your web server:
Expand Down Expand Up @@ -282,11 +282,11 @@ $rule = $rules->getRule(942100);

## OWASP Diagnostics Header

Enable the diagnostics header to see which OWASP rule matched:
Enable response and diagnostic headers to see which OWASP rule matched:

```php
$config->enableResponseHeaders();
$config->enableOwaspDiagnosticsHeader();
$config->enableDiagnosticsHeaders();
```

When an OWASP rule blocks a request, the response includes:
Expand All @@ -298,7 +298,7 @@ X-Phirewall-Owasp-Rule: 942100
```

::: info
`X-Phirewall` and `X-Phirewall-Matched` require `enableResponseHeaders()`. The `X-Phirewall-Owasp-Rule` header is controlled independently by `enableOwaspDiagnosticsHeader()`.
`X-Phirewall` and `X-Phirewall-Matched` require `enableResponseHeaders()`. The `X-Phirewall-Owasp-Rule` header is controlled independently by `enableDiagnosticsHeaders()` (`enableOwaspDiagnosticsHeader()` is a deprecated alias): the CRS matcher declares it via the generic `diagnostic_headers` metadata key on its `MatchResult`, so it also appears when the matcher is used as a Fail2Ban filter.
:::

::: info
Expand Down
Loading