Releases: flowd/phirewall
0.6.0
Phirewall 0.6.0
Warning
0.6.0 is a breaking release. It removes several APIs and extracts the OWASP CRS engine into a separate Composer package. Review the upgrade notes below before updating. Every breaking change is also documented in detail in the CHANGELOG.
⚠️ Breaking changes & upgrade notes
1. OWASP CRS engine moved to a companion package (#95)
The Flowd\Phirewall\Owasp\ namespace (SecRuleLoader, CoreRuleSet, CoreRuleSetMatcher, the operator/variable collectors) and the $config->blocklists->owasp() shortcut have been removed from core. OWASP CRS support now lives in flowd/phirewall-preset-owasp-crs, which ships the engine under Flowd\PhirewallPresetOwaspCrs\Engine\ plus ready-made per-paranoia-level presets.
composer require flowd/phirewall-preset-owasp-crs// Option A - apply a ready-made CRS preset:
use Flowd\PhirewallPresetOwaspCrs\Presets;
use Flowd\PhirewallPresetOwaspCrs\ParanoiaLevel;
$config = $config->with(Presets::blocklist(ParanoiaLevel::Level1));
// Option B - register a rule set yourself (replaces $config->blocklists->owasp(...)):
use Flowd\Phirewall\Config\Rule\BlocklistRule;
use Flowd\PhirewallPresetOwaspCrs\Engine\CoreRuleSetMatcher;
$config->blocklists->addRule(new BlocklistRule('owasp', new CoreRuleSetMatcher($ruleSet)));The OWASP diagnostics header (X-Phirewall-Owasp-Rule, enableOwaspDiagnosticsHeader()) stays in core and works unchanged with the companion engine.
2. Config composition unified behind Config::with(ConfigLayer ...) (#96)
Config::combine() and Config::mergedWith() have been removed and Config::compose() is now private. Both a live Config and a PortableConfig implement the new Flowd\Phirewall\ConfigLayer interface, so all layers compose through one call.
// Before: $config->combine($portableA, $portableB); / $config->mergedWith($overlay);
// After:
$config = $config->with($layerA, $layerB); // layers apply left to right, later wins by rule name3. $config->safelists->trustedBots() removed (#97)
The reverse-DNS TrustedBotMatcher stays in core; wire it directly.
use Flowd\Phirewall\Config\Rule\SafelistRule;
use Flowd\Phirewall\Matchers\TrustedBotMatcher;
$config->safelists->addRule(new SafelistRule(
'trusted-bots',
new TrustedBotMatcher(ipResolver: $config->getIpResolver(), cache: $cache),
));Pass ipResolver: $config->getIpResolver() to keep the proxy-aware behaviour trustedBots() had by default; omit it to key on REMOTE_ADDR. Wiring the matcher directly also lets you rate-limit verified bots instead of safelisting them.
4. Route-scoped apiRateLimiting() / loginProtection() presets removed (#85)
These presets and their constants (API_RATE_LIMITING, LOGIN_PROTECTION, API_PATH_PREFIX, LOGIN_PATH_PREFIX, LOGIN_FAILURE_RULE) hardcoded application routes (/api, /login) and have been removed. Build API rate limiting and login brute-force protection as plain Config instead (see examples/03-api-rate-limiting.php and examples/02-brute-force-protection.php). The remaining universal presets are scannerBlocking() and sensitivePathBlocking().
5. Behavioural: IP-aware matchers now late-bind the client-IP resolver (#99)
IpMatcher, TrustedBotMatcher, FileIpBlocklistMatcher, and SnapshotBlocklistMatcher now implement ClientIpResolverAware. When constructed without an explicit $ipResolver, they resolve the client IP through the Config they run under at request time - instead of the REMOTE_ADDR they captured at construction. This makes IP rules composition-correct, but it is a behavioural change: a matcher built with no explicit resolver inside a Config that has an IP resolver set now uses that resolver. Pass an explicit resolver to opt out. (SafelistSection/BlocklistSection also no longer take a Config back-reference.)
What's Changed
⚠️ Breaking changes
- Drop the route-scoped apiRateLimiting and loginProtection presets by @sascha-egerer in #85
- Unify config composition behind Config::with(ConfigLayer) by @sascha-egerer in #96
- Extract the OWASP CRS engine into flowd/phirewall-preset-owasp-crs by @sascha-egerer in #95
- Remove the safelists->trustedBots() convenience method by @sascha-egerer in #97
- Late-bind the client-IP resolver into IP-aware matchers by @sascha-egerer in #99
Fixes & hardening
- Validate file blocklist entries as IP or CIDR on write by @sascha-egerer in #92
- Skip the htaccess rewrite when the blocked-IP set is unchanged by @sascha-egerer in #93
- Reject line breaks in PatternEntry value and target by @sascha-egerer in #94
- Flatten nested ARGS parameters when collecting OWASP variable values by @sascha-egerer in #89
- Fix @rx evaluator to inspect oversized values and fail closed on engine errors by @sascha-egerer in #88
- Store the ban registry as a native array so it round-trips on every cache backend by @sascha-egerer in #90
Documentation & examples
- Load the portable-config database example per request by @sascha-egerer in #86
- Document the fail-open and header-key skip behaviours by @sascha-egerer in #91
Internal & packaging
- Open 0.6.0 development: alias dev-main to 0.6.x-dev by @sascha-egerer in #87
- Cover the OWASP diagnostics header emission in BlocklistEvaluator by @sascha-egerer in #98
- Suggest the bad-IP and bot preset packages by @sascha-egerer in #100
- Finalize the 0.6.0 changelog entry by @sascha-egerer in #101
Full Changelog: 0.5.0...0.6.0
0.5.0
What's Changed
- Add SECURITY.md by @sascha-egerer in #44
- Bound BanManager registry lifetime with a TTL by @sascha-egerer in #45
- Re-throw RedisCache::increment errors instead of returning 0 by @sascha-egerer in #46
- RequestContext: optional key, recordHit() for allow2ban by @sascha-egerer in #47
- Extract a curated subset in the observability examples by @sascha-egerer in #48
- TrustedProxyResolver: keep rightmost N entries when truncating chains by @sascha-egerer in #49
- TrustedProxyResolver: resolve bracketed IPv6+port forms by @sascha-egerer in #50
- Add KeyExtractors::hashedHeader for sensitive headers by @sascha-egerer in #52
- Default TrustedProxyResolver to a single allowed header by @sascha-egerer in #51
- Confine @pmFromFile resolution to its context folder by @sascha-egerer in #53
- Serialize file-store writers via a sidecar lock by @sascha-egerer in #55
- Serialise ApacheHtaccessAdapter writers via a sidecar flock by @sascha-egerer in #54
- Document the REMOTE_ADDR default on file-blocklist and infra listener by @sascha-egerer in #56
- Switch fail2ban Quick-Start and example 02 to RequestContext by @sascha-egerer in #57
- Read only the last X-Forwarded-For / Forwarded header instance by @sascha-egerer in #58
- Canonicalise IPv6 in IpMatcher and TrustedProxyResolver by @sascha-egerer in #59
- Restrict header_equals in safelists and switch to hash_equals by @sascha-egerer in #60
- Add signed transport for PortableConfig by @sascha-egerer in #61
- Promote PortableConfig to a first-class transport by @sascha-egerer in #62
- Compose and layer multiple Configs into one effective Config by @sascha-egerer in #63
- Add Presets and materialize PortableConfig via Config::combine() by @sascha-egerer in #64
- Validate cache keys per PSR-16 in all store backends by @sascha-egerer in #65
- Cache the parsed pattern snapshot on the blocklist read hot path by @sascha-egerer in #69
- Cap OWASP CRS per-variable values with a fail-closed, configurable limit by @sascha-egerer in #70
- Extract a shared quote-aware scanner in SecRuleParser by @sascha-egerer in #71
- Enable PHP CS Fixer fully_qualified_strict_types by @sascha-egerer in #73
- Batch ban-key existence checks in the fail2ban and allow2ban evaluators by @sascha-egerer in #72
- Remove the unused PresetUpdateChecker seam by @sascha-egerer in #76
- Use null (not []) for SuspiciousHeaders default header set by @sascha-egerer in #74
- Require an explicit BanType on isBanned() by @sascha-egerer in #75
- Flatten all XFF / Forwarded header instances before the trusted-hop walk by @sascha-egerer in #77
- Share PSR-16 bulk cache operations with per-key failure reporting by @sascha-egerer in #78
- Name CacheKeyGenerator's key-truncation budget constants by @sascha-egerer in #79
- Remove the deprecated DeprecatedConfigMethods trait from Config by @sascha-egerer in #81
- Default rule key to the configured client IP when omitted by @sascha-egerer in #82
- Add dev-main branch-alias by @KamiYang in #80
- Document allow2ban in the setDiscriminatorNormalizer applies-to list by @sascha-egerer in #83
- Finalize the 0.5.0 CHANGELOG by @sascha-egerer in #84
New Contributors
Full Changelog: 0.4.0...0.5.0
0.4.0
What's Changed
- Add phpt integration tests for all core features by @sascha-egerer in #37
- Fix broken documentation links in README by @sascha-egerer in #38
- Fix RegexEvaluator treating literal first/last chars as PCRE delimiters by @sascha-egerer in #39
- Unify ban-evaluator threshold semantics to >= by @sascha-egerer in #40
- Fix SecRuleParser stripping literal quotes from
@rxargs by @sascha-egerer in #41 - Defer DeprecatedConfigMethods removal from 0.4 to 0.5 by @sascha-egerer in #42
- Date CHANGELOG for 0.4.0 release by @sascha-egerer in #43
Full Changelog: 0.3.0...0.4.0
0.3.0
Phirewall 0.3.0
Phirewall is a PSR-15 middleware that protects PHP applications from brute force, DDoS, SQL injection, XSS, and bot
attacks.
This is a major feature release with 36 pull requests — new protection layers, a cleaner architecture, stronger security
defaults, and a brand-new documentation site.
Documentation
Full documentation is now available at phirewall.de — covering getting started, framework
integration (Laravel, Symfony, Slim, Mezzio, TYPO3), all features, advanced topics, common attack recipes, and FAQ.
Highlights
New Protection Layers
- Allow2Ban — hard volume cap with auto-ban after threshold, complementing Fail2Ban
- Known Scanner Blocker — block sqlmap, nikto, nmap, burp, and other attack tools by User-Agent
- Trusted Bot Verification — safelist Googlebot, Bingbot, etc. via reverse DNS
- Suspicious Headers Detection — block requests missing standard browser headers
- IP/CIDR Matcher — safelist or block by IP range
Smarter Rate Limiting
- Sliding window strategy for smoother throttling alongside fixed-window
- Multi-window throttle — burst + sustained limits in a single rule (e.g., 5 req/s + 200 req/min)
- Dynamic limits — vary limits per request via closure (e.g., by user plan)
- Track with threshold — passive counting with optional alerting
New Storage Backend
- PdoCache — SQL-backed persistence for MySQL, PostgreSQL, and SQLite
Security Hardening
- Response headers are now opt-in —
X-Phirewall/X-Phirewall-Matchedno longer leak information by default
(breaking change) - ReDoS length guard — OWASP
@rxoperator skips values >8 KiB to prevent catastrophic regex backtracking - Path traversal prevention —
@pmFromFileoperator blocks directory traversal - Redis failure visibility —
RedisCache::increment()emitsE_USER_WARNINGinstead of failing silently
Architecture
- Evaluator classes —
Firewall::decide()refactored into dedicated per-rule-type evaluators (SRP) - PatternKind enum — string-backed enum replacing class constants
- PSR-17 response factories — framework-native responses via standard factories
- RequestContext — post-handler fail2ban signaling from your login handler
- Fail-open/fail-closed — configurable middleware error handling (fail-open by default)
Observability
- DiagnosticsCounters — lightweight in-memory event counters for all rule types including Allow2Ban
- DiagnosticsDispatcher — observe events and forward to a real PSR-14 dispatcher
Breaking Changes
X-PhirewallandX-Phirewall-Matchedresponse headers are no longer added by default. Call
$config->enableResponseHeaders()to re-enable them.- Fail2Ban/Allow2Ban threshold semantics corrected: ban triggers after N failures, not on the Nth attempt.
Deprecated
Config::add*()/Config::get*()methods — use the section API instead ($config->safelists->add(),
$config->blocklists->owasp(), etc.). Will be removed in 0.4.
Install / Upgrade
composer require flowd/phirewall:^0.3What's Changed
- Refactor Config into section objects with PSR-14 diagnostics by @sascha-egerer in #1
- Add tests: InMemoryPatternBackend, benchmarks, edge cases, value objects by @sascha-egerer in #2
- Fix broken REQUEST_REGEX matching and O(n²) pruning by @sascha-egerer in #3
- Add KnownScannerMatcher and blocklists->knownScanners() by @sascha-egerer in #5
- Add TrustedBotMatcher, SuspiciousHeadersMatcher, IpMatcher, and shared matcher utilities by @sascha-egerer in #6
- Harden allow2ban: validation, security, performance, BanManager API by @sascha-egerer in #7
- Add sliding window throttle with strategy pattern by @sascha-egerer in #8
- Add multiThrottle, dynamic throttle limits, disable toggle, and discriminator normalizer by @sascha-egerer in #9
- Add track threshold with optional limit parameter and thresholdReached flag by @sascha-egerer in #10
- Add PdoCache storage backend, reset helpers, and database test infrastructure by @sascha-egerer in #11
- Add fail-open/fail-closed middleware error handling by @sascha-egerer in #12
- Add comprehensive infrastructure adapter tests with vfsStream by @sascha-egerer in #13
- Add PSR-17 response factory support by @sascha-egerer in #14
- Update documentation and examples to current API by @sascha-egerer in #15
- Modernize examples and extract DecisionPath enum by @sascha-egerer in #16
- Improve documentation: middleware ordering, TYPO3 example, terminology by @sascha-egerer in #17
- Add consistent constructor validation across all rule types by @sascha-egerer in #18
- Resolve discriminator normalizer once at top of decide() by @sascha-egerer in #21
- Cache OWASP operator preprocessing and prevent @pmFromFile path traversal by @sascha-egerer in #20
- Replace hardcoded credentials with getenv() in PdoCache example by @sascha-egerer in #19
- Add request attribute API for post-handler fail2ban signaling by @sascha-egerer in #22
- Replace local docs with links to phirewall.de by @sascha-egerer in #24
- Make X-Phirewall/X-Phirewall-Matched response headers opt-in by @sascha-egerer in #25
- Fix fail2ban/allow2ban threshold off-by-one: ban after N failures, not on Nth by @sascha-egerer in #26
- Extract CoreRule operator and variable logic into strategy classes by @sascha-egerer in #28
- Extract shared fail2ban increment-and-ban logic by @sascha-egerer in #29
- Separate DiagnosticsCounters (observer) from event dispatching by @sascha-egerer in #30
- Extract Firewall::decide() into dedicated evaluator classes per rule type by @sascha-egerer in #31
- Refactor PatternKind to string-backed enum and deduplicate PatternEntry logic by @sascha-egerer in #32
- Add Allow2BanBanned event tracking to DiagnosticsCounters by @sascha-egerer in #33
- Add ReDoS length guard to RegexEvaluator (8 KiB limit) by @sascha-egerer in #34
- Surface RedisCache::increment() failures via trigger_error() by @sascha-egerer in #35
- Add CHANGELOG.md for 0.3.0 release by @sascha-egerer in #36
Full Changelog: 0.2.0...0.3.0
0.2.0
Add In-Memory-Pattern-Backend and improve documentation
Full Changelog: 0.1.1...0.2.0
Release Notes – Version 0.1.1
- Add proper error message if APCu is missing
Release Notes – Version 0.1.0
Snapshot
Phirewall ships as a lean PSR‑15 middleware firewall for PSR‑7 stacks, pairing safelists, blocklists, throttles, Fail2Ban, and telemetry hooks without extra runtime baggage.
What’s fresh
- Drop‑in middleware: auto-discovers PSR‑17 factories and runs in Slim, Mezzio, Laminas, Symfony, or bespoke pipelines.
- Composable controls: safelist bypasses, targeted blocklists, per-key throttles with Retry‑After, Fail2Ban bans, and observability-only track hooks.
- Pattern sources: mix IP/CIDR, path, header, regex, OWASP CRS adapters, and plug them into file/Redis/DB loaders.
- Signals & tracing: PSR‑14 events, optional X‑RateLimit headers, diagnostics counters, and ready-made Monolog plus OpenTelemetry recipes.
- Storage freedom: in-memory, APCu, Redis, or any PSR‑16 cache with automatic key normalization and namespacing.
- Infrastructure bridges: Apache
.htaccessadapter, nonblocking runners, and extensibleInfrastructureBlockerInterfacetargets.
Where to use it
- Protect APIs with layered burst/sustained budgets per token, IP, or user.
- Harden login/admin flows with combined track, throttle, and Fail2Ban policies.
- Shield webhooks by blocking suspicious signatures while collecting counters for forensics.
- Mirror application bans to server ACLs or feed structured events into SIEM pipelines.