kanopi/firewall-symfony
Symfony integration for Kanopi Firewall that adds configurable request firewalls and access rules, letting you allow or deny traffic by IP, network, or custom criteria. Protect routes and enforce access policies directly from Symfony configuration.
What this release is. kanopi/firewall-symfony v1.3.0 is a security-control bundle (WAF-style rule engine + ALTCHA challenge page) wrapping the kanopi/firewall library. It is a Symfony bundle, not a Laravel package. That distinction is the single most important fact in this assessment and should be confirmed before any effort is committed.
Architecture fit.
kanopi:firewall:check CLI gate) is squarely relevant to our stack: edge-adjacent request filtering, bot/facet-crawl mitigation, and a CI-assertable access policy.composer require into a Laravel app will not wire rules, listeners, or the challenge controller.kanopi/firewall directly and write a thin Laravel service provider + middleware adapter ourselves; (b) run it only in a Symfony-fronted service (e.g. a gateway/edge app); (c) vendor the library's rule evaluation into existing middleware. Option (a) is the only path that gets the value inside Laravel, and it is our code, not theirs.mode: exception" and writes all responses itself — meaning the parts we would reuse in Laravel are the library, and the bundle is mostly response-writing and console wiring. That reduces the reuse surface but also reduces coupling risk: we would be depending on the more portable layer.Technical risk (ranked).
kanopi:firewall:check previously exited 2 ("challenged") when configuration was missing or unreadable. It now exits 64. Any pipeline gate asserting "URL is challenged" would have passed on a totally unconfigured app. That is a real security blind spot that this release fixes, but it also means pipelines written against the old codes will now fail (correctly). Audit every CI job that consumes this exit code before upgrading; treat 0/1/2/3/64/70 as a contract and pin it in our test fixtures.^2.33 → ^2.36). Several fixes make rules that never fired start firing:
asn:16509) previously never matched — any ASN rule in our config now begins blocking traffic for the first time.//wp-json/… and /%77p-json/… evasions are caught. Expect new blocks on previously-passing traffic, including legitimate double-slash URLs emitted by some clients/proxies.^2.36 will happily float to 2.4x/2.5x, and this release is direct evidence that library patch releases change rule behaviour. A ^ floor on a security control is an unpriced maintenance liability; we should lock and review, not float.private, no-store, no-cache, must-revalidate, max-age=0, Pragma, Expires: 0, Surrogate-Control: no-store, CDN-Cache-Control: no-store. Correct for the bug, but it is a behavioural change for any downstream cache (Varnish, Fastly, Cloudflare, nginx proxy_cache) and may interact with existing surrogate-key/purge logic. Also note the fix depends on the CDN honouring these headers — this is mitigation, not enforcement.symfony/* component versions are transitively pinned by the framework; a direct requirement on this bundle can force a Laravel major bump or a conflict. This must be resolved on paper before any install attempt.query_count (new rule variable) is a capability, not a risk, but it implies config work to cap facet crawling — scope it as a separate, optional item.Key questions to answer before approval.
CDN-Cache-Control / Surrogate-Control the way Fastly/Pantheon does? Have we reproduced the #18 loop, or is this a fix for a bug we never had?^ version constraints for request-path security dependencies?mode: exception off / rule disable) and its tested runbook?Stack fit — the honest read. As-is: not a drop-in fit for Laravel. Fit for a Symfony service: strong. The value we can actually bank in Laravel is the library layer (kanopi/firewall ^2.36) plus our own middleware; the bundle's own fixes (response header set, doctor warning, CLI exit codes, challenge notices) are only directly available if we run the bundle. This should be stated to stakeholders plainly rather than discovered mid-sprint.
Compatibility gates (do these first, in order).
symfony/* component versions. Compare against PHP 8.1–8.5 / Symfony 6.4·7.4·8.1. Any mismatch is a stop condition, not a workaround.composer why-not kanopi/firewall-symfony ^1.3 in a throwaway branch of the real app. Treat the output as the go/no-go artefact.Migration path.
kanopi:firewall:check; list ASN rules, capitalised rate-limit keys, and any rule with a path that could be normalised differently.blocked_response: http_exception error page. Verify cache behaviour at the edge with a real challenged URL under load (the #18 repro: two concurrent visitors, one challenged URL).query_count rules for facet crawling as a separate, individually reversible change.Sequencing rationale. Dry-run before CI changes (so a rules regression isn't masked by a gate that now behaves differently). CI changes before production (so the gate is trustworthy when you need it). Header verification before production because the header set is the actual fix, and it is only correct if the CDN honours it.
Rollback. Composer-pinned previous version (^1.2), rules held in config not code where possible, and a documented "disable rule" path. Confirm the downgrade does not re-expose the #18 cache loop in a way that would force us to stay forward-only during an incident.
Effort estimate. ~3–5 engineer-days for the Symfony-native path (upgrade, dry-run, CI, verification). Add 5–10 days if a Laravel adapter must be written and maintained — and that estimate should be owned by the team that will carry it, not by this assessment.
Maintenance burden. Low code surface, non-trivial policy surface. The package is a security control at the request path; it will require an owner, an upgrade cadence, and a review process for rule changes. This release demonstrates why: a library patch moved ASN matching, path normalisation and rate-limit keying without any change on our side. Budget a recurring review — monthly dependency review, quarterly rule audit — not a one-off install.
Version policy (recommendation). Pin the library exactly or with a tight ~ range and upgrade deliberately with dry-run diffs. Floating ^2.36 on request-path security logic is not an acceptable risk posture and should be written into the assessment outcome, not left as a team preference.
Support and vendor risk. Single-vendor, small-team package. We should (a) confirm we can read and patch the library if needed (MIT-style licence, small codebase — verify), (b) document our own runbook rather than depending on upstream docs, and (c) keep an independent outer layer (edge WAF/rate limiting) so a package defect is degraded, not catastrophic. The #18 bug is the case study: an upstream header omission silently converted a security challenge into a denial-of-service loop for authenticated-looking traffic, and only a library patch release fixed it.
Scaling characteristics. Rule evaluation is per-request CPU work in the application tier — cheap but not free, and it is the tier you least want to add work to under load. Challenge pages shift load to the edge/browser but, as #18 showed, only if the CDN behaves. Two specific scaling notes: the fixed header set disables caching on every firewall-written response (fine, but do not let someone "optimise" it back), and per-field rate-limit counters now mean counters multiply with the number of keyed fields — size any shared counter store accordingly.
Failure modes to design for.
global.path_source: script_name — the new doctor warning exists because this silently prefixes every path with the subdirectory in a non-WordPress app. Run the doctor step in CI, not just locally.Ramp-up. For a Symfony team: half a day to a day — config is declarative, the CLI (kanopi:firewall:check) is self-documenting once the exit codes are documented, and the doctor command covers the main footgun. For a Laravel team writing an adapter: 3–5 days of pairing, plus a written internal doc, because the upstream README assumes Symfony wiring throughout and will not answer the questions that actually come up. Two things to teach explicitly: (1) the exit-code contract, because it is a CI gate people will otherwise misread; (2) the response-header set, because it is load-bearing and looks like boilerplate someone will want to trim.
Recommendation. Adopt on the Symfony path, with the CI exit-code update and a staged dry-run as non-negotiable prerequisites. On the Laravel path, treat this as evaluate-and-probably-adapt, not adopt: the library is worth the dependency, the bundle is not usable without our own middleware, and that middleware needs a named owner before approval. In both cases, pin the version, monitor block rate and challenge solve rate as first-class signals, and keep an independent outer control so this package is a defence in depth rather than the only lock on the door.
How can I help you explore Laravel packages today?