Skip to content

spatie/laravel-csp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Set content security policy headers in a Laravel app

Latest Version on Packagist GitHub Workflow Status Check & fix styling Total Downloads

By default, all scripts on a webpage are allowed to send and fetch data to any site they want. This can be a security problem. Imagine one of your JavaScript dependencies sends all keystrokes, including passwords, to a third party website.

It's very easy for someone to hide this malicious behaviour, making it nearly impossible for you to detect it (unless you manually read all the JavaScript code on your site). For a better idea of why you really need to set content security policy headers, read this excellent blog post by David Gilbertson.

Setting Content Security Policy headers helps solve this problem. These headers dictate which sites your site is allowed to contact. This package makes it easy for you to set the right headers.

This readme does not aim to fully explain all the possible usages of CSP and its directives. We highly recommend that you read Mozilla's documentation on the Content Security Policy before using this package. Another good resource to learn about CSP, is this edition of the Larasec newsletter by Stephen Rees-Carter.

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Installation

You can install the package via composer:

composer require spatie/laravel-csp

You can publish the config-file with:

php artisan vendor:publish --tag=csp-config

This is the contents of the file which will be published at config/csp.php:

return [

    /*
     * Presets will determine which CSP headers will be set. A valid CSP preset is
     * any class that extends `Spatie\Csp\Preset`
     */
    'presets' => [
        Spatie\Csp\Presets\Basic::class,
    ],

    /**
     * Register additional global CSP directives here.
     */
    'directives' => [
        // Directive::SCRIPT => [Keyword::UNSAFE_EVAL, Keyword::UNSAFE_INLINE],
    ],

    /*
     * These presets which will be put in a report-only policy. This is great for testing out
     * a new policy or changes to existing CSP policy without breaking anything.
     */
    'report_only_presets' => [
        //
    ],

    /**
     * Register additional global report-only CSP directives here.
     */
    'report_only_directives' => [
        // Directive::SCRIPT => [Keyword::UNSAFE_EVAL, Keyword::UNSAFE_INLINE],
    ],

    /*
     * All violations against a policy will be reported to this url.
     * A great service you could use for this is https://report-uri.com/
     */
    'report_uri' => env('CSP_REPORT_URI', ''),

    /*
     * Headers will only be added if this setting is set to true.
     */
    'enabled' => env('CSP_ENABLED', true),

    /*
     * The class responsible for generating the nonces used in inline tags and headers.
     */
    'nonce_generator' => Spatie\Csp\Nonce\RandomString::class,

    /*
     * Set false to disable automatic nonce generation and handling.
     * This is useful when you want to use 'unsafe-inline' for scripts/styles
     * and cannot add inline nonces.
     * Note that this will make your CSP policy less secure.
     */
    'nonce_enabled' => env('CSP_NONCE_ENABLED', true),
];

You can add CSP headers to all responses of your app by registering Spatie\Csp\AddCspHeaders::class as global middleware in bootstrap/app.php.

use Spatie\Csp\AddCspHeaders;

->withMiddleware(function (Middleware $middleware) {
     $middleware->append(AddCspHeaders::class);
})

Alternatively you can apply the middleware on the route or route group level.

// In your routes file
Route::get('my-page', 'MyController')
    ->middleware(AddCspHeaders::class);

You can also pass a preset class as a parameter to the middleware:

// In your routes file
Route::get('my-page', 'MyController')
    ->middleware(AddCspHeaders::class . ':' . MyPreset::class);

The given preset will override the ones configured in the config/csp.php config file for that specific route or group of routes.

Alternatively, you can register your CSP policies as a meta tag using our Blade directives.

{{-- app/layout.blade.php --}}
<head>
    @cspMetaTag
</head>

Usage

This package ships with a few commonly used presets to get your started. We're happy to receive PRs for more services!

Policy Services
Basic Allow requests to scripts, images… within the application
AdobeFonts fonts.adobe.com (previously typekit.com)
Fathom usefathom.com
Google Google Analytics & Tag Manager
GoogleFonts fonts.google.com
HubSpot hubspot.com (full suite)
JsDelivr jsdelivr.com
Tolt tolt.io

Register the presets you want to use for your application in config/csp.php under the presets or report_only_presets key.

If you have app-specific needs or the service you're integrated isn't included in this package, you can create your own preset as explained below. You can also register global directives in the configuration file using a tuple notation.

'directives' => [
    [Directive::SCRIPT, Keyword::UNSAFE_EVAL],
],

'report_only_directives' => [
    [Directive::SCRIPT, Keyword::UNSAFE_INLINE],
],

Here you may also create multiple directive & value combinations by padding multiple values in the tuple.

'directives' => [
    [[Directive::SCRIPT, Directive::STYLE], [Keyword::UNSAFE_EVAL, Keyword::UNSAFE_INLINE]],
],

Creating a preset

An example of a CSP directive is script-src. If this has the value 'self' www.google.com then your site can only load scripts from its own domain or www.google.com. You'll find a list with all CSP directives at Mozilla's excellent developer site.

According to the spec certain directive values need to be surrounded by quotes. Examples of this are 'self', 'none' and 'unsafe-inline'. When using add function you're not required to surround the directive value with quotes manually. We will automatically add quotes. Script/style hashes, as well, will be auto-detected and surrounded with quotes.

public function configure(Policy $policy): void
{
    $policy
        // Will output `'self'` when outputting headers
        ->add(Directive::SCRIPT, Keyword::SELF)
        // Will output `'sha256-hash'` when outputting headers
        ->add(Directive::STYLE, 'sha256-hash');
}

You may also use the same keywords for multiple directives by passing an array of directives.

public function configure(Policy $policy): void
{
    $policy->add([Directive::SCRIPT, DIRECTIVE::STYLE], 'www.google.com');
}

Or multiple keywords for one or more directives.

public function configure(Policy $policy): void
{
    $policy
        ->add(Directive::SCRIPT, [Keyword::UNSAFE_EVAL, Keyword::UNSAFE_INLINE]],)
        ->add([Directive::SCRIPT, DIRECTIVE::STYLE], ['www.google.com', 'analytics.google.com']);
}

There are also a few cases where you don't have to or don't need to specify a value, eg. upgrade-insecure-requests, block-all-mixed-content, ... In this case you can use the following value:

public function configure(Policy $policy): void
{
    $policy
        ->add(Directive::UPGRADE_INSECURE_REQUESTS, Value::NO_VALUE)
        ->add(Directive::BLOCK_ALL_MIXED_CONTENT, Value::NO_VALUE);
}

This will output a CSP like this:

Content-Security-Policy: upgrade-insecure-requests;block-all-mixed-content

The presets key of the csp config file is set to [\Spatie\Csp\Presets\Basic::class] by default. This class allows your site to only use images, scripts, form actions of your own site.

namespace Spatie\Csp\Presets;

use Spatie\Csp\Directive;
use Spatie\Csp\Keyword;
use Spatie\Csp\Policy;
use Spatie\Csp\Preset;

class Basic implements Preset
{
    public function configure(Policy $policy): void
    {
        $policy
            ->add(Directive::BASE, Keyword::SELF)
            ->add(Directive::CONNECT, Keyword::SELF)
            ->add(Directive::DEFAULT, Keyword::SELF)
            ->add(Directive::FORM_ACTION, Keyword::SELF)
            ->add(Directive::IMG, Keyword::SELF)
            ->add(Directive::MEDIA, Keyword::SELF)
            ->add(Directive::OBJECT, Keyword::NONE)
            ->add(Directive::SCRIPT, Keyword::SELF)
            ->add(Directive::STYLE, Keyword::SELF)
            ->addNonce(Directive::SCRIPT)
            ->addNonce(Directive::STYLE);
    }
}

You can allow fetching scripts from www.google.com by writing a custom preset.

namespace App\Support;

use Spatie\Csp\Directive;
use Spatie\Csp\Policy;

class MyCspPreset implements Preset
{
    public function configure(Policy $policy): void
    {
        $policy->add(Directive::SCRIPT, 'www.google.com');
    }
}

Don't forget to update the presets key in the csp config file to the class name of your preset.

'presets' => [
    Spatie\Csp\Presets\Basic::class,
    App\Support\MyCspPreset::class,
],

Using inline scripts and styles

When using CSP you must specifically allow the use of inline scripts or styles. The recommended way of doing that with this package is to use a nonce. A nonce is a number that is unique per request. The nonce must be specified in the CSP headers and in an attribute on the html tag. This way an attacker has no way of injecting malicious scripts or styles.

First you must add the nonce to the right directives in your policy:

public function configure(Policy $policy): void
{
    $this
        ->add(Directive::SCRIPT, 'self')
        ->add(Directive::STYLE, 'self')
        ->addNonce(Directive::SCRIPT)
        ->addNonce(Directive::STYLE);
}

Next you must add the nonce to the html:

<style nonce="@cspNonce">
   ...
</style>

<script nonce="@cspNonce">
   ...
</script>

There are few other options to use inline styles and scripts. Take a look at the CSP docs on the Mozilla developer site to know more.

Integration with Vite

When building assets, Laravel's Vite plugin can generate a nonce that you can retrieve with Vite::cspNonce. You can use in your own NonceGenerator.

namespace App\Support;

use Illuminate\Support\Str;
use Illuminate\Support\Facades\Vite;

class LaravelViteNonceGenerator implements NonceGenerator
{
    public function generate(): string
    {
        return Vite::cspNonce();
    }
}

Don't forget to specify the fully qualified class name of your NonceGenerator in the nonce_generator key of the csp config file.

Alternatively, you can instruct Vite to use a specific value that it should use as nonce.

namespace App\Support;

use Illuminate\Support\Str;
use Illuminate\Support\Facades\Vite;

class RandomString implements NonceGenerator
{
    public function generate(): string
    {
        // Determine the value for `$myNonce` however you want
        $myNonce = '';
    
        Vite::useCspNonce($myNonce);
        
        return $myNonce;
    }
}

Outputting a CSP Policy as a meta tag

In rare circumstances, a large site may have so many external connections that the CSP header actually exceeds the max header size. Or you might be generating a static page with Laravel and don't have control over the headers when the response is sent. Thankfully, the CSP specification allows for outputting information as a meta tag in the head of a webpage.

This package provides a @cspMetaTag blade directive that you may place in the <head> of your site. This will render a header for all configured presets (both default and report-only).

<head>
    @cspMetaTag
</head>

You may also use this tag to render a specific preset.

<head>
    @cspMetaTag(App\Support\MyCustomPreset::class)
</head>

Or use the @cspMetaTagReportOnly tag to render a specific preset in report-only mode.

<head>
    @cspMetaTagReportOnly(App\Support\MyCustomPreset::class)
</head>

Reporting CSP errors

In the browser

Instead of outright blocking all violations, you can put configure a CSP policy in report only mode by registering presets in the report_only_presets configuration option. In this case all requests will be made, but all violations will display in your favourite browser's console.

To an external url

Any violations against the policy can be reported to a given url. You can set that url in the report_uri key of the csp config file. A great service that is specifically built for handling these violation reports is http://report-uri.io/.

Testing

You can run all the tests with:

composer test

Changelog

Please see CHANGELOG for more information what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you've found a bug regarding security please mail security@spatie.be instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.