phil / browser-security-bundle
The protections a Symfony application asks of the browser, adjustable page by page: Content Security Policy with nonces, report-only and a bounded report endpoint, the neighbouring headers, HSTS and an HTTPS redirect, X-Frame-Options by path, signed cookies, external redirects refused, and Clear-Sit
Package info
github.com/parler-haut-interagir-librement/browser-security-bundle
Type:symfony-bundle
pkg:composer/phil/browser-security-bundle
Requires
- php: >=8.5
- ext-ctype: *
- ext-mbstring: *
- ext-sodium: *
- ext-uri: *
- phil/rate-limit: ^0.1@dev
- psr/clock: ^1.0
- psr/event-dispatcher: ^1.0
- symfony/config: ^8.2
- symfony/dependency-injection: ^8.2
- symfony/http-foundation: ^8.2
- symfony/http-kernel: ^8.2
- symfony/routing: ^8.2
Requires (Dev)
- doctrine/dbal: ^4.5
- doctrine/doctrine-bundle: ^3.3
- doctrine/orm: ^3.7
- phil/ip-anonymizer: ^0.1@dev
- phil/rate-limit-bundle: ^0.1@dev
- phil/structured-data-bundle: ^0.1@dev
- phpunit/phpunit: ^13.4
- psr/cache: ^3.0
- psr/log: ^3.0
- symfony/asset-mapper: ^8.2
- symfony/cache: ^8.2
- symfony/clock: ^8.2
- symfony/console: ^8.2
- symfony/event-dispatcher: ^8.2
- symfony/event-dispatcher-contracts: ^3.6
- symfony/framework-bundle: ^8.2
- symfony/rate-limiter: ^8.2
- symfony/security-bundle: ^8.2
- symfony/security-http: ^8.2
- symfony/service-contracts: ^3.6
- symfony/twig-bridge: ^8.2
- symfony/twig-bundle: ^8.2
- twig/twig: ^3.21
Suggests
- doctrine/dbal: For the violations kept in the table phil_csp_report
- doctrine/orm: For the table added to the schema the ORM generates
- phil/ip-anonymizer: For the client's address kept truncated with each violation
- phil/rate-limit-bundle: To count the report bodies with a named limiter, beside the others of the application
- psr/cache: For the deduplication of the events in a pool shared by the workers
- psr/log: For the warning of an external redirect
- symfony/asset-mapper: For the nonce on the tags of importmap()
- symfony/clock: For the clock service the reports are dated with
- symfony/console: For phil:csp:purge-reports
- symfony/rate-limiter: For the throttle of the report endpoint, which the bundle refuses to build without unless it is disabled
- symfony/security-http: For Clear-Site-Data at logout
- symfony/twig-bridge: For the nonce on the tags of importmap()
- symfony/web-profiler-bundle: For the profiler panel of the policy
- twig/twig: For csp_nonce() and csp_meta()
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-12 23:52:45 UTC
README
The protections a Symfony application asks of the browser, in one bundle: one configuration, adjustable page by page.
phil/browser-security-bundle writes the Content-Security-Policy header of every response from policies declared in the
configuration, directive by directive, and checked when the container is built: a source written without its quotes,
a stray ; or an unknown directive fails the build, not a page. Each request gets its own nonces, created when a
template first asks for one: csp_nonce('script') and csp_nonce('style') in Twig, the tags of AssetMapper's
importmap() — through which Stimulus and Turbo load —, and the csp-nonce meta tag Turbo reads. A stricter policy can
run beside the enforced one as Content-Security-Policy-Report-Only.
Beside it come the headers a site wants on every response: Permissions-Policy, the three Cross-Origin-*-Policy
headers and the report-only forms of two of them, Referrer-Policy, X-Frame-Options by path, nosniff,
X-Permitted-Cross-Domain-Policies, Origin-Agent-Cluster and Integrity-Policy, offered off. It also redirects plain
requests to HTTPS and sends HSTS, signs the cookies it is told to, refuses a redirect to a host not allowed, and has
Symfony's listener clear the site data at logout on every firewall. A file someone uploaded is served with a policy of
its own, applied by route, by controller or by response: a sandboxed policy that loads nothing, nosniff, and an
attachment unless its type cannot run.
Every one of these is global in the configuration and adjustable for one page: a route default, a controller attribute or a service adds, replaces or removes it for its own responses. The bundle builds on what Symfony does already and says so, topic by topic.
The reports come back to an endpoint that reads both formats — application/csp-report and the Reporting API's
application/reports+json —, bounded in size and in rate, deduplicated, with the client's address never kept raw.
Each new violation dispatches an event, a DBAL table can keep them with their count, and the profiler shows the policy
of each response.
Status
| Part | State |
|---|---|
| Policies by directive, checked at build time, named policies and inheritance | Implemented and tested |
| Overrides by route default, controller attribute and service | Implemented and tested |
Nonces for Twig, importmap() and Turbo, 'strict-dynamic', hashes, Trusted Types |
Implemented and tested |
| Enforced and report-only policies side by side | Implemented and tested |
Cache-Control: private on a response whose policy lists a nonce |
Implemented and tested |
| The report endpoint counted by a named limiter of phil/rate-limit-bundle | Implemented and tested |
| Report endpoint, both formats, the reports of the other policies, bounds, throttle, deduplication, anonymized address, event | Implemented and tested, fuzzed |
| DBAL store of the violations and its purge command | Implemented and tested |
| Neighbouring security headers, by path and by page, profiler panel | Implemented and tested |
| Overrides of every protection by route default, controller attribute and service | Implemented and tested |
HTTPS redirect and HSTS, signed cookies, external redirects, Clear-Site-Data at logout |
Implemented and tested |
| The policy of user content: strict headers for uploaded files, by route, attribute or response | Implemented and tested |
| Building the headers of a response | Benchmarked |
Documentation
| Document | What it covers |
|---|---|
| Installation | Requirements, the bundle, the route of the reports, the table. |
| Configuration | Every option of phil_browser_security. |
| Policies | Directives and their values, the default policy, named policies, inheritance, hashes, Trusted Types. |
| Overrides | Add, replace and remove, for the policy and every other protection: attributes, route defaults, services. |
| Nonces | csp_nonce(), csp_meta(), importmap(), Turbo, Stimulus, 'strict-dynamic', caching. |
| Deployment | Report-only first, then enforced, with both headers at once. |
| Reports | The endpoint, the two formats, the bounds, the throttle, deduplication, privacy, the event, the store. |
| Neighbouring headers | Every header beside the policy, frames by path, integrity, Clear-Site-Data at logout. |
| HTTPS | The redirect, HSTS, a proxy, and what Symfony does already. |
| Signed cookies | Signing, rotating the secret, one page. |
| External redirects | The allowed hosts, the three actions, one page. |
| User content | The strict headers of an uploaded file, by route, by controller or by response. |
| Worker mode and profiler | What lives where during a request, kernel.reset, the profiler panel. |
| Extension points | The ports, services and events an application uses or replaces. |
| Testing | The tests, the mutation bar, the fuzz target and the benchmark. |
| Reference | Every type of the bundle, with its role. |
Quick start
composer require phil/browser-security-bundle
# config/packages/phil_browser_security.yaml phil_browser_security: policies: default: directives: default-src: ["'self'"] img-src: ["'self'", 'data:'] object-src: ["'none'"] base-uri: ["'self'"] frame-ancestors: ["'self'"] strict_dynamic: true reporting: storage: true
# config/routes/phil_browser_security.yaml phil_browser_security: resource: '@PhilBrowserSecurityBundle/config/routes.php'
<head> {{ csp_meta() }} {{ importmap('app') }} <style nonce="{{ csp_nonce('style') }}">body { margin: 0 }</style> </head>
Requirements
PHP 8.5 or later with ctype, mbstring, sodium and uri, Symfony 8.2 or later, and phil/rate-limit. Twig,
AssetMapper, the rate limiter, a PSR-6 pool, Doctrine DBAL, the console, security-http and phil/ip-anonymizer are
optional, each enabling one part.
Contributing
Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.
License
MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.