v2.10.0
One change with a config surface: global.behind_proxy lets an operator assert whether a proxy sits in front of the deployment, which is the fact the library cannot detect for itself. If you run without a proxy, this is the release that lets you stop the per-request trusted-proxies warning honestly.
No behaviour changes unless you set the new key. Existing configuration keeps working, and with behind_proxy unset the trusted-proxies check does exactly what 2.9.0 did — same level, same message text plus one sentence pointing at the new setting.
PHP 8.1–8.5 supported.
Added
-
global.behind_proxy(#101, fixes #99). Every plugin reads$request->getClientIp(), and Symfony only honours proxy headers once you have calledRequest::setTrustedProxies(...). The firewall warns when that list is empty, because a deployment that is behind a proxy without it can have its source IP spoofed past IP allowlists and per-IP rate limits.What was missing was any way to say "there is no proxy here". Two settings covered three real postures, so a site with nothing in front of it logged a
warningon every request with no way to stop it —create()runs per request, and the only escape was passingsetTrustedProxies()a value that was not true.global: behind_proxy: false # asserted: nothing in front of this deployment
behind_proxyTrusted proxies empty ⇒ unset (default) warning— the posture is unknown, unchanged from 2.9.0falsesilent — nothing can spoof a forwarding header through a proxy that does not exist trueerror— an asserted proxy that is not wired up is a definite misconfiguration, not an open questionTwo details are load-bearing enough to state here rather than leave in the docs:
-
behind_proxy: falsewins overrequire_trusted_proxies: true. An explicit assertion that there is no proxy makes the requirement moot. Throwing anyway would leave an operator who told the truth about their deployment with no way to start the firewall at all. -
An uninterpretable value fails safe.
behind_proxy:with nothing after it parses to NULL, and an%env()%token for an unset variable yields an empty string; PHP'sfilter_var()reads both as FALSE. Taken literally, a half-written key would silence a security warning. Both are treated as "posture unknown" instead, so the warning stands. Silencing a security warning is the dangerous direction to guess in.
require_trusted_proxiesis unchanged and still escalates whichever case applies into a startupConfigurationException. -
Fixes
- The
create()docblock promised a silencer that did not exist (#101, fixes #99). It read "Operators who know they're not behind a proxy can silence the warning withglobal.require_trusted_proxies: false(the default)" — andfalseis precisely the branch that emits the warning. The method's own docblock anddocs/configuration/global.mdwere both already correct, which is how one wrong comment survived between two accurate ones. Anyone who followed it went looking for a setting that was not there, which is what #99 reported.
Changed
-
The default trusted-proxies message gained one sentence naming
global.behind_proxy=falseas the way to assert no proxy. The rest of the text, and itswarninglevel, are unchanged. -
docs/configuration/global.mdTrusted Proxies section rewritten to cover the two settings separately:behind_proxyfor the deployment fact,require_trusted_proxiesfor how loud the unresolved cases are, with the precedence rule between them stated explicitly.
Upgrade notes
-
Nothing is required.
composer update kanopi/firewallchanges no behaviour on its own. Withbehind_proxyunset the check behaves exactly as it did in 2.9.0. -
If nothing sits in front of your site, set
behind_proxy: false. This is the fix for the per-request warning reported in #99. It is the only supported way to silence it — there is deliberately no way to suppress it by accident. -
If you are behind a proxy / CDN / load balancer, say so.
behind_proxy: trueupgrades a missingsetTrustedProxies()fromwarningtoerror, because once you have declared the proxy the omission is a defect rather than an unknown. Pair it withrequire_trusted_proxies: trueto fail the deploy instead:global: behind_proxy: true require_trusted_proxies: true
-
The new
errorlevel cannot affect an existing deployment. It applies only tobehind_proxy: true, a key that did not exist before this release.
Internal
-
behind_proxyis resolved beforecreate()runsarray_filter()over the global config, mirroring howrequire_configis already handled.array_filter()strips falsy values, so abehind_proxy: falseread any later is deleted outright and becomes indistinguishable from unset — the setting's entire purpose silently gone. The test suite caught this rather than review; the first implementation read it inside the check and the "silences the warning" test failed immediately. -
11 new unit tests cover each of the three postures, the precedence rule against
require_trusted_proxies, the boolean-ish string forms ("false","0","no","off") that arrive from%env()%tokens and quoted YAML, and the empty / unparseable values that must not be read as a false assertion. The three pre-existing #58 trusted-proxies regressions are unmodified and still pass, which is what pins the unchanged default behaviour. -
Per-process log deduplication was considered and rejected. It was one of the options in #99. It would add process-global mutable state to a security component in order to reduce the volume of a message that can now be switched off properly. The remaining per-request warning is the genuinely-unknown case, and that should keep asking until it is answered — the difference in this release is that it is now answerable from config.
-
Docs deployment now publishes with
ghp-importrather thanmkdocs gh-deploy(#98), andpymdown-extensionswas bumped to 11.0 (#97). Both are build-tooling changes with no effect on the released package. -
Suite: 845 unit tests and 49 integration tests pass. PHPCS, PHPStan at level max and Rector are clean. The 2 reported deprecations are a pre-existing
ReflectionProperty::setValue()call inLoggingFactoryTest, unrelated to this release.