mulertech / csp-bundle
Symfony bundle for Content Security Policy (CSP) header management with nonce support
Requires
- php: >=8.2
- symfony/config: ^6.4 || ^7.0
- symfony/dependency-injection: ^6.4 || ^7.0
- symfony/event-dispatcher: ^6.4 || ^7.0
- symfony/http-foundation: ^6.4 || ^7.0
- symfony/http-kernel: ^6.4 || ^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.5 || ^12.0
- symfony/routing: ^6.4 || ^7.0
- twig/twig: ^3.0
Suggests
- symfony/routing: Required to use route-based CSP violation reporting
- twig/twig: Required to use the csp_nonce() Twig function
README
Symfony bundle for Content Security Policy (CSP) header management with named nonce support.
Installation
composer require mulertech/csp-bundle
Configuration
The bundle ships with secure defaults for all directives. You only need to override what differs from the defaults.
Minimal config/packages/mulertech_csp.yaml:
mulertech_csp: directives: script-src: - "'self'" - "nonce(main)" style-src: - "'self'" - "'unsafe-inline'"
Full reference
Here is the complete list of available options with their default values:
mulertech_csp: enabled: true # true by default report_only: false # false by default always_add: [] # Origins added to ALL directives report: url: ~ # External URL for report-uri/report-to route: ~ # Symfony route name (alternative to url) route_params: [] # Route parameters chance: 100 # 0-100, % of requests with reporting directives: # Only override what you need default-src: - "'self'" script-src: - "'self'" - "nonce(main)" style-src: - "'self'" - "'unsafe-inline'" img-src: - "'self'" - "data:" font-src: - "'self'" connect-src: - "'self'" media-src: - "'self'" object-src: - "'none'" frame-src: - "'none'" frame-ancestors: - "'none'" base-uri: - "'self'" form-action: - "'self'" upgrade-insecure-requests: true
Default directives
| Directive | Default |
|---|---|
default-src |
'self' |
script-src |
'self' + nonce(main) |
style-src |
'self' 'unsafe-inline' |
img-src |
'self' data: |
font-src |
'self' |
connect-src |
'self' |
media-src |
'self' |
object-src |
'none' |
frame-src |
'none' |
frame-ancestors |
'none' |
base-uri |
'self' |
form-action |
'self' |
upgrade-insecure-requests |
true |
Named nonces
Use nonce(handle) syntax in directives to create named nonces:
mulertech_csp: directives: script-src: - "'self'" - "nonce(main)" # For your main scripts - "nonce(analytics)" # For analytics scripts
Each named nonce generates a unique 256-bit (32 bytes) cryptographically secure value.
always_add
Add origins to all directives automatically (except those set to 'none'):
mulertech_csp: always_add: - "https://cdn.example.com" directives: default-src: - "'self'" object-src: - "'none'" # always_add is NOT merged here
Violation reporting
Report CSP violations to an external endpoint:
mulertech_csp: report: url: "https://report.example.com/csp" chance: 50 # Only 50% of requests
Or use a Symfony route:
mulertech_csp: report: route: "app_csp_report" route_params: {}
Report-only mode
Test your CSP policy without enforcing it:
mulertech_csp: report_only: true
This sets the Content-Security-Policy-Report-Only header instead of Content-Security-Policy.
Usage
In Twig templates
Use the csp_nonce('handle') function with a named handle:
<script nonce="{{ csp_nonce('main') }}"> // Your inline JavaScript </script> <script nonce="{{ csp_nonce('analytics') }}"> // Analytics script </script>
Dynamic CSP customization
Listen to the BuildCspHeaderEvent to customize CSP per-request:
use MulerTech\CspBundle\Event\BuildCspHeaderEvent; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; #[AsEventListener(event: BuildCspHeaderEvent::NAME)] class CspListener { public function __invoke(BuildCspHeaderEvent $event): void { if ($event->getRequest()->getPathInfo() === '/admin') { $event->setHeaderValue("default-src 'self'; script-src 'self'"); } } }
Inject the nonce generator
use MulerTech\CspBundle\CspNonceGenerator; class MyService { public function __construct( private readonly CspNonceGenerator $nonceGenerator, ) {} public function getMainNonce(): string { return $this->nonceGenerator->getNonce('main'); } }
Upgrading from v1.x
Breaking changes
- Directives format: Changed from scalar strings to arrays of sources
# v1.x mulertech_csp: directives: script-src: "'self' 'nonce-{nonce}'" # v2.0 mulertech_csp: directives: script-src: - "'self'" - "nonce(main)"
- Twig function:
csp_nonce()now requires a handle argument
{# v1.x #} <script nonce="{{ csp_nonce() }}"> {# v2.0 #} <script nonce="{{ csp_nonce('main') }}">
- Nonce placeholder:
{nonce}replaced bynonce(handle)syntax
Requirements
- PHP >= 8.2
- Symfony 6.4 or 7.x
- Twig (optional, for the
csp_nonce()function) - symfony/routing (optional, for route-based reporting)
License
MIT