Skip to content
HELMET Source Back to HELMET
Menu

Customizing the CSP#

The default policy is a starting point, not a cage. Pass a csp policy (raw PHP) or a second constructor argument (middleware) and it is merged over the default, per directive: a directive you set replaces the default's value for that directive, and a directive you leave out keeps the default. The default itself, exactly as the code defines it, is on The headers.

Merging over the default#

To remove a directive the default sets, set it to null:

$helmet = helmet_boot([
    'preset' => 'citadel',
    'csp' => [
        // Replace the default img-src.
        'img-src' => ["'self'", 'data:'],
        // Remove form-action entirely.
        'form-action' => null,
    ],
]);

In Laravel the same array goes in helmet.csp (published config) or HELMET_CSP (JSON in the environment); see Laravel. The merge rule is identical on both paths, because both call the same core function.

Reporting setup#

By default the policy is enforced: a violation is blocked. To see what would be blocked before you enforce it, or to keep collecting reports after you do, point the policy at a report endpoint.

Set csp_report_uri (raw PHP) or HELMET_CSP_REPORT_URI (Laravel) to the URL browsers POST reports to. It must be https. HELMET then adds report-uri and report-to to the policy and sends a Reporting-Endpoints header naming the endpoint. Set csp_report_only (or HELMET_CSP_REPORT_ONLY) to true to send the policy as Content-Security-Policy-Report-Only, which observes violations without blocking them.

$helmet = helmet_boot([
    'preset' => 'citadel',
    'csp_report_uri' => 'https://app.example.edu/csp-report',
    'csp_report_only' => true,
]);

Receiving the reports in Laravel#

Laravel ships an opt-in endpoint that receives the reports and logs one line per violation (the violated directive, the blocked URI's host, and the document path) and nothing else. It accepts both report formats: the legacy application/csp-report object, and the Reporting API's application/reports+json array, from which it logs the csp-violation entries (at most 20 per POST; browsers batch other report types into the same request, and those are ignored). The route is off until you register it:

// routes/web.php
Route::cspReport();                 // POST /csp-report
Route::cspReport('/reports/csp');   // or your own path

The endpoint caps the body at 16 KiB, returns 204, and carries no authentication of its own; wrap it in your own middleware if you want to keep it private. Browsers POST reports without a CSRF token, so the route excludes the web group's CSRF middleware. Because the endpoint is public and its input is attacker-influenced, it is throttled by default to 60 reports a minute per IP (throttle:60,1); pass a second argument to use a different throttle, or null to disable it:

Route::cspReport('/reports/csp', 'throttle:120,1'); // your own limit
Route::cspReport('/reports/csp', null);             // no throttle