Symfony bundle for Content Security Policy (CSP) header management with named nonce support.
composer require mulertech/csp-bundleThe 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'"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| 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 |
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 scriptsEach named nonce generates a unique 256-bit (32 bytes) cryptographically secure value.
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 hereReport CSP violations to an external endpoint:
mulertech_csp:
report:
url: "https://report.example.com/csp"
chance: 50 # Only 50% of requestsOr use a Symfony route:
mulertech_csp:
report:
route: "app_csp_report"
route_params: {}Test your CSP policy without enforcing it:
mulertech_csp:
report_only: trueThis sets the Content-Security-Policy-Report-Only header instead of Content-Security-Policy.
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>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'");
}
}
}use MulerTech\CspBundle\CspNonceGenerator;
class MyService
{
public function __construct(
private readonly CspNonceGenerator $nonceGenerator,
) {}
public function getMainNonce(): string
{
return $this->nonceGenerator->getNonce('main');
}
}- 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
- PHP >= 8.2
- Symfony 6.4 or 7.x
- Twig (optional, for the
csp_nonce()function) - symfony/routing (optional, for route-based reporting)
MIT