Skip to content

0.10.0

Latest

Choose a tag to compare

@sascha-egerer sascha-egerer released this 07 Aug 10:26
c8c60ef

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

  • scope filter on the typed throttle API (#124). throttles->add(), sliding() and multi() accept an optional scope closure 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 the scope already supported by ThrottleRule and PortableConfig::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 option blockOnSignalBan) now optionally makes the Middleware replace the handler response with the regular blocked response when the signal crosses its rule's threshold and bans the key. A fail2ban block carries the X-Phirewall headers when response headers are enabled; an allow2ban block additionally sends Retry-After with 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 blocked FirewallResult when the signal banned (previously void), null otherwise. 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. SnapshotBlocklistMatcher now follows Config::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 the PATH_REGEX, REQUEST_REGEX and HEADER_REGEX sites via the new RegexMatcher::matchesFailClosed(). The policy reaches the matcher through the new Matchers\FailOpenAware capability. Default behavior is unchanged; generic regex filters (the PortableConfig filter factories, also used by safelists) are unchanged as well, and the OWASP CRS engine's @rx operator keeps its own unconditional fail-closed handling.

What's Changed

Full Changelog: 0.9.0...0.10.0