Phirewall 0.10.0
No breaking changes. Two new capabilities around throttling and post-handler signals, and one behavioral fix in pattern-blocklist regex matching. Everything is also documented in the CHANGELOG.
✨ New capabilities
-
scopefilter on the typed throttle API (#124).throttles->add(),sliding()andmulti()accept an optionalscopeclosure that restricts which requests the throttle counts; non-matching requests skip the rule entirely. Combined with an omitted$key, the rule counts per client IP through the Config IP resolver, replacing the null-returning key closure pattern that bypassed the resolver.multi()applies the scope to every window sub-rule. This exposes thescopealready supported byThrottleRuleandPortableConfig::throttle()on the typed section API.// Only /search requests count, keyed per resolved client IP $config->throttles->add('search', limit: 10, period: 60, scope: fn($request) => $request->getUri()->getPath() === '/search', );
-
Opt-in 403 for the request whose signal imposed a ban (#126, #127). By default, a post-handler
RequestContext::recordFailure()/recordHit()signal only counts and may ban, but never blocks the current request.Config::enableBlockOnSignalBan()(portable optionblockOnSignalBan) now optionally makes theMiddlewarereplace the handler response with the regular blocked response when the signal crosses its rule's threshold and bans the key. A fail2ban block carries theX-Phirewallheaders when response headers are enabled; an allow2ban block additionally sendsRetry-Afterwith the full ban duration.Note: the handler has already run at that point. The flag changes only the outgoing response; it does not stop the application from processing the request. Abort in the handler when processing must stop with the failure.
Firewall::processRecordedSignal()now returns the blockedFirewallResultwhen the signal banned (previouslyvoid),nullotherwise. Plain callers are unaffected.
🔧 Behavioral fix
- Pattern-blocklist regex matching honours the failure policy on a PCRE engine error (#125, #128). A compile-valid pattern that errors at match time (for example the backtrack limit exceeded) previously counted as "no match" unconditionally.
SnapshotBlocklistMatchernow followsConfig::setFailOpen(): under the fail-open default an engine error still counts as no match (a broken pattern must not block legitimate traffic), while a fail-closed firewall (setFailOpen(false)) treats the error as a match at thePATH_REGEX,REQUEST_REGEXandHEADER_REGEXsites via the newRegexMatcher::matchesFailClosed(). The policy reaches the matcher through the newMatchers\FailOpenAwarecapability. Default behavior is unchanged; generic regex filters (thePortableConfigfilter factories, also used by safelists) are unchanged as well, and the OWASP CRS engine's@rxoperator keeps its own unconditional fail-closed handling.
What's Changed
- Add scope filter to the typed throttle API by @sascha-egerer in #124
- Honour the fail-open policy on blocklist regex engine errors by @sascha-egerer in #125
- Add opt-in 403 for the request whose signal imposed a ban by @sascha-egerer in #126
- Clarify that a signal ban 403 does not stop the handler by @sascha-egerer in #127
- Rebind the pattern backend failure policy via useFailOpen() by @sascha-egerer in #128
Full Changelog: 0.9.0...0.10.0