PHPackages                             aubes/csp-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. aubes/csp-bundle

ActiveSymfony-bundle[Security](/categories/security)

aubes/csp-bundle
================

Symfony bundle for Content-Security-Policy headers management with multi-group policies, nonces, hashes and reporting

v2.0.0(2mo ago)2111[2 PRs](https://github.com/aubes/csp-bundle/pulls)MITPHPPHP &gt;=8.2CI passing

Since Apr 20Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/aubes/csp-bundle)[ Packagist](https://packagist.org/packages/aubes/csp-bundle)[ RSS](/packages/aubes-csp-bundle/feed)WikiDiscussions main Synced today

READMEChangelog (3)Dependencies (31)Versions (8)Used By (0)

Content Security Policy Bundle
==============================

[](#content-security-policy-bundle)

[![CI](https://github.com/aubes/csp-bundle/actions/workflows/php.yml/badge.svg)](https://github.com/aubes/csp-bundle/actions/workflows/php.yml)[![Latest Stable Version](https://camo.githubusercontent.com/2283a05675f33744a7159bca7f51f303703b026869785b174835917b85ca309e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f61756265732f6373702d62756e646c65)](https://packagist.org/packages/aubes/csp-bundle)[![PHP Version](https://camo.githubusercontent.com/a602a59a4e978070ca95e945411966b50f6a9a2dd12e6a97164d618982f51d63/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f61756265732f6373702d62756e646c652f706870)](https://packagist.org/packages/aubes/csp-bundle)[![License](https://camo.githubusercontent.com/cf8ebbf37254b0429a58f2f85049cb9fb45734c7a555c39f5a02ccd327c9dc80/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f61756265732f6373702d62756e646c65)](https://packagist.org/packages/aubes/csp-bundle)

Symfony bundle for managing [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) headers.

- **Presets** to get started in seconds (`strict`, `permissive`, `api`)
- **Nonces and hashes** via Twig helpers and block tags
- **Multi-group policies** with per-controller PHP attributes
- **Audit command** (`csp:check`) to detect misconfigurations
- **Violation reporting** with Reporting-Endpoints and legacy Report-To support
- **Worker-ready** (FrankenPHP / RoadRunner)

**Requirements:** PHP &gt;= 8.2, Symfony ^6.4 | ^7.4 | ^8.0

Installation
------------

[](#installation)

```
composer require aubes/csp-bundle
```

For Twig nonce/hash support:

```
composer require symfony/twig-bundle
```

Configuration
-------------

[](#configuration)

```
# config/packages/csp.yaml
csp:
    # Required when multiple groups are defined
    # When only one group is defined, it becomes the default
    default_group: default

    # Automatically add default group CSP headers to every response
    auto_default: true

    # Force report-only mode on all groups (useful in dev)
    debug: false

    groups:
        default:
            # Use a preset as a base (strict, permissive, api)
            preset: strict

            # Report-Only instead of enforcing
            report_only: false

            # Additional policies (merged with preset)
            policies:
                connect_src:
                    - self
                    - 'https://api.example.com'

        admin:
            preset: permissive
            policies:
                img_src:
                    - self
                    - 'https://cdn.example.com'

        api:
            preset: api
```

### Presets

[](#presets)

Three built-in presets provide sensible defaults:

PresetDescription`strict`Nonce-based with `strict-dynamic`, `object-src 'none'`, `base-uri 'none'``permissive``'self'` + `'unsafe-inline'`, suitable for legacy apps that cannot use nonces`api``default-src 'none'`, no framing, no formsPreset policies are merged with your custom policies. Your policies extend the preset, they don't replace it.

> **Note:** The `strict` preset uses `strict-dynamic` which requires nonces to work. Without nonces, all scripts will be blocked. Make sure you use the Twig nonce helpers in your templates:
>
> ```
> {# Block tag (recommended) #}
> {% csp_script %}
>     document.getElementById('app').textContent = 'Hello!';
> {% end_csp_script %}
>
> {# Or manual nonce attribute #}
>
>     // ...
>
> ```

### Directive names

[](#directive-names)

Use underscore-separated names in YAML configuration:

```
policies:
    script_src: [self]
    style_src_elem: [self]
    frame_ancestors: [self]
    upgrade_insecure_requests: []
```

Usage
-----

[](#usage)

### Applying CSP headers

[](#applying-csp-headers)

#### Auto default

[](#auto-default)

With `auto_default: true`, the default group is applied to every response automatically.

#### PHP attributes

[](#php-attributes)

Use attributes on controllers to select groups or disable CSP:

```
use Aubes\CSPBundle\Attribute\CSPGroup;
use Aubes\CSPBundle\Attribute\CSPDisabled;

// Apply a specific group
#[CSPGroup('admin')]
class AdminController extends AbstractController
{
    // All actions use the "admin" CSP group
}

// Apply one enforcing + one report-only group
#[CSPGroup('default')]
#[CSPGroup('strict_ro')]
class DashboardController extends AbstractController {}

// Override at method level
class PageController extends AbstractController
{
    #[CSPGroup('strict')]
    public function secure(): Response {}
}

// Disable CSP entirely
#[CSPDisabled]
class WebhookController extends AbstractController {}
```

> **Multi-group constraint:** each request supports at most one enforcing group and one report-only group. Applying two groups of the same mode (e.g. two enforcing groups) will throw a `LogicException`. This is by design: the HTTP spec says multiple `Content-Security-Policy` headers are intersected (most restrictive wins), which would silently break additive policies.

#### Route defaults

[](#route-defaults)

You can also configure groups via route defaults:

```
# config/routes.yaml
admin_route:
    path: /admin
    defaults:
        _csp_groups: [admin]

webhook_route:
    path: /webhook
    defaults:
        _csp_disabled: true
```

#### Dynamic directives

[](#dynamic-directives)

Add directives at runtime from a controller:

```
use Aubes\CSPBundle\CSP;

class ExampleController extends AbstractController
{
    public function __invoke(CSP $csp): Response
    {
        // Add to default group
        $csp->addDirective('script-src', 'https://cdn.example.com');

        // Add to a specific group
        $csp->addDirective('img-src', 'https://images.example.com', 'admin');

        return $this->render('example.html.twig');
    }
}
```

### Twig: nonces

[](#twig-nonces)

Nonces are generated per-request and automatically added to the CSP header.

#### Block tags (recommended)

[](#block-tags-recommended)

Wraps your inline script/style with a nonce automatically:

```
{% csp_script %}
    document.getElementById('app').textContent = 'Hello!';
{% end_csp_script %}

{% csp_style %}
    body { font-family: sans-serif; }
{% end_csp_style %}

{# With a specific group #}
{% csp_script 'admin' %}
    console.log('admin');
{% end_csp_script %}
```

#### Functions

[](#functions)

For manual nonce attributes:

```
{# script-src nonce #}

    // ...

{# style-src nonce #}

    body { font-family: sans-serif; }

{# Generic nonce with directive #}

    // ...

{# With a specific group #}

    // ...

```

### Twig: hashes

[](#twig-hashes)

Register a hash for inline content without using a nonce:

```
{% do csp_hash('script-src', "alert('hello')") %}

{# With a specific algorithm (default: sha256) #}
{% do csp_hash('script-src', content, 'sha384') %}

{# With a specific group #}
{% do csp_hash('script-src', content, 'sha256', 'admin') %}
```

Reporting
---------

[](#reporting)

### Configuration

[](#configuration-1)

Enable [Reporting-Endpoints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Reporting-Endpoints) (modern) and optionally [Report-To](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Report-To) (legacy):

```
# config/packages/csp.yaml
csp:
    groups:
        default:
            reporting:
                max_age: 3600
                # Optional: override the group name in the report-to directive
                group_name: ~
                # Emit legacy Report-To header alongside Reporting-Endpoints
                backward_compatibility: false
                endpoints:
                    - csp_report  # Symfony route name
```

> **Note:** Multiple endpoints can be configured, but only the first one is used with the modern `Reporting-Endpoints` header (which supports a single URL per group). Additional endpoints are only used when `backward_compatibility: true` is enabled, as the legacy `Report-To` header supports failover across multiple URLs.

### Built-in report controller

[](#built-in-report-controller)

A controller is included to receive CSP violation reports (path: `/csp-report/{group}`, route: `csp_report`). Each report is dispatched as a `CSPViolationEvent`.

Import the bundle's routing:

```
# config/routes.yaml
csp_report:
    resource: '@CSPBundle/Resources/config/routing.yaml'
```

Or define the route manually:

```
# config/routes.yaml
csp_report:
    path: /csp-report/{group}
    controller: Aubes\CSPBundle\Controller\ReportController::__invoke
    methods: ['POST']
```

### Handling violation reports

[](#handling-violation-reports)

Listen to `CSPViolationEvent` to handle reports however you want (log, Sentry, database, Slack, etc.):

```
use Aubes\CSPBundle\Event\CSPViolationEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener]
class CSPViolationListener
{
    public function __invoke(CSPViolationEvent $event): void
    {
        // $event->group  : the CSP group name
        // $event->report : the sanitized report data
    }
}
```

### Built-in logger

[](#built-in-logger)

Optionally enable the built-in log listener to log violations via Monolog:

```
# config/packages/csp.yaml
csp:
    report_logger:
        logger_id: ~  # Logger service ID (default: logger)
        level: ~      # Log level (default: WARNING)
```

Without `report_logger`, events are dispatched but not logged.

Audit command
-------------

[](#audit-command)

Audit your CSP configuration against common security pitfalls:

```
php bin/console csp:check
```

The command checks for: missing critical directives, `unsafe-inline`/`unsafe-eval` usage, wildcard sources, HTTP/IP sources, `strict-dynamic` without nonce, trusted types consistency, and missing reporting.

```
 ------- ------- ----------- -----------------------------------------------------------
  Level   Group   Directive   Finding
 ------- ------- ----------- -----------------------------------------------------------
  ERROR   weak    script-src  'unsafe-inline' allows execution of arbitrary inline scripts.
  WARN    weak    object-src  object-src should be 'none' unless plugins are explicitly needed.
  INFO    default report-to   No reporting endpoint configured.
 ------- ------- ----------- -----------------------------------------------------------

```

Web Debug Toolbar
-----------------

[](#web-debug-toolbar)

When `symfony/web-profiler-bundle` is installed, a CSP panel is available in the Symfony profiler showing:

- Active group(s) and enabled/disabled status
- Full CSP header value
- Parsed directives with color-coded sources (keywords, nonces, hashes, URLs)
- Report-Only header if present
- Reporting endpoints

Debug mode
----------

[](#debug-mode)

Set `debug: true` to force all groups into `report-only` mode. Useful during development to detect violations without breaking the page:

```
# config/packages/csp.yaml
when@dev:
    csp:
        debug: true
```

Gradual rollout
---------------

[](#gradual-rollout)

Hardening your CSP without breaking your site is easy with multi-group policies. The idea: enforce a permissive policy today, evaluate a strict one in parallel, then switch when ready.

### Step 1: enforce permissive, evaluate strict

[](#step-1-enforce-permissive-evaluate-strict)

```
csp:
    default_group: default
    groups:
        default:
            preset: permissive

        strict:
            preset: strict
            report_only: true
            reporting:
                max_age: 3600
                endpoints:
                    - csp_report
```

Apply both groups to your controllers:

```
#[CSPGroup('default')]
#[CSPGroup('strict')]
class MyController extends AbstractController {}
```

The `permissive` group enforces (nothing breaks), while `strict` runs in report-only mode. Violations are sent to your reporting endpoint.

### Step 2: fix violations

[](#step-2-fix-violations)

Review reports and add nonces to your inline scripts and styles:

```
{% csp_script %}
    document.getElementById('app').textContent = 'Hello!';
{% end_csp_script %}
```

### Step 3: switch to strict

[](#step-3-switch-to-strict)

Once reports are clean, promote `strict` to enforcing and remove the permissive group:

```
csp:
    default_group: default
    groups:
        default:
            preset: strict
            reporting:
                max_age: 3600
                endpoints:
                    - csp_report
```

Worker mode (FrankenPHP / RoadRunner)
-------------------------------------

[](#worker-mode-frankenphp--roadrunner)

The `CSP` service implements `ResetInterface`. Nonces and dynamic directives are automatically reset between requests in long-running processes.

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance86

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~544 days

Total

3

Last Release

82d ago

Major Versions

v1.1.0 → v2.0.02026-04-13

PHP version history (2 changes)v1.0.0PHP &gt;=7.4

v2.0.0PHP &gt;=8.2

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/3941035?v=4)[A. Bes](/maintainers/aubes)[@aubes](https://github.com/aubes)

---

Top Contributors

[![aubes](https://avatars.githubusercontent.com/u/3941035?v=4)](https://github.com/aubes "aubes (8 commits)")

---

Tags

bundlecontent-security-policycspphpsymfony-bundlesymfonybundlesecuritycspcontent-security-policynonce

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Psalm

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/aubes-csp-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/aubes-csp-bundle/health.svg)](https://phpackages.com/packages/aubes-csp-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.9M388](/packages/easycorp-easyadmin-bundle)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.6M572](/packages/shopware-core)[symfony/web-profiler-bundle

Provides a development tool that gives detailed information about the execution of any request

2.3k160.5M1.2k](/packages/symfony-web-profiler-bundle)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M738](/packages/sylius-sylius)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M203](/packages/sulu-sulu)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
