Content Security Policy

What is Content Security Policy?

One of the best protections you have against XSS attacks is to implement a Content Security Policy (CSP) on the site. This requires you to specify and authorize each source of content that is included in your site’s HTML, including images, stylesheets, JavaScript files, and so on. The browser will reject content from sources that are not explicitly approved. This authorization is defined within the response’s Content-Security-Policy header and offers various configuration options.

This sounds complex, and on some sites, can definitely be challenging. For many simple sites, though, where all content is served by the same domain (http://example.com), it is very simple to integrate.

As this is a complex subject, this user guide will not go over all of the details. For more information, you should visit the following sites:

Turning CSP On

Important

The Debug Toolbar may use Kint, which outputs inline scripts. Therefore, when CSP is turned on, CSP nonce is automatically output for the Debug Toolbar. However, if you are not using CSP nonce, this will change the CSP header to something you do not intend, and it will behave differently than in production; if you want to verify CSP behavior, turn off the Debug Toolbar.

By default, support for this is off. To enable support in your application, edit the CSPEnabled value in app/Config/App.php:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class App extends BaseConfig
{
    // ...

    public bool $CSPEnabled = true;
}

When enabled, the response object will contain an instance of CodeIgniter\HTTP\ContentSecurityPolicy. The values set in app/Config/ContentSecurityPolicy.php are applied to that instance, and if no changes are needed during runtime, then the correctly formatted header is sent and you’re all done.

With CSP enabled, two header lines are added to the HTTP response: a Content-Security-Policy header, with policies identifying content types or origins that are explicitly allowed for different contexts, and a Content-Security-Policy-Report-Only header, which identifies content types or origins that will be allowed but which will also be reported to the destination of your choice.

Our implementation provides for a default treatment, changeable through the reportOnly() method. When an additional entry is added to a CSP directive, as shown below, it will be added to the CSP header appropriate for blocking or preventing. That can be overridden on a per call basis, by providing an optional second parameter to the adding method call.

Runtime Configuration

If your application needs to make changes at run-time, you can access the instance at $this->response->getCSP() in your controllers. The class holds a number of methods that map pretty clearly to the appropriate header value that you need to set. Examples are shown below, with different combinations of parameters, though all accept either a directive name or an array of them:

<?php

// get the CSP instance
$csp = $this->response->getCSP();

// specify the default directive treatment
$csp->reportOnly(false);

// specify the origin to use if none provided for a directive
$csp->setDefaultSrc('cdn.example.com');

// specify the URL that "report-only" reports get sent to
$csp->setReportURI('http://example.com/csp/reports');

// specify that HTTP requests be upgraded to HTTPS
$csp->upgradeInsecureRequests(true);

// add types or origins to CSP directives
// assuming that the default treatment is to block rather than just report
$csp->addBaseURI('example.com', true); // report only
$csp->addChildSrc('https://youtube.com'); // blocked
$csp->addConnectSrc('https://*.facebook.com', false); // blocked
$csp->addFontSrc('fonts.example.com');
$csp->addFormAction('self');
$csp->addFrameAncestor('none', true); // report this one
$csp->addImageSrc('cdn.example.com');
$csp->addMediaSrc('cdn.example.com');
$csp->addManifestSrc('cdn.example.com');
$csp->addObjectSrc('cdn.example.com', false); // reject from here
$csp->addPluginType('application/pdf', false); // reject this media type
$csp->addScriptSrc('scripts.example.com', true); // allow but report requests from here
$csp->addStyleSrc('css.example.com');
$csp->addSandbox(['allow-forms', 'allow-scripts']);

The first parameter to each of the “add” methods is an appropriate string value, or an array of them.

The reportOnly() method allows you to specify the default reporting treatment for subsequent sources, unless over-ridden. For instance, you could specify that youtube.com was allowed, and then provide several allowed but reported sources:

<?php

// get the CSP instance
$csp = $this->response->getCSP();

$csp->addChildSrc('https://youtube.com'); // allowed
$csp->reportOnly(true);
$csp->addChildSrc('https://metube.com'); // allowed but reported
$csp->addChildSrc('https://ourtube.com', false); // allowed

Inline Content

It is possible to set a website to not protect even inline scripts and styles on its own pages, since this might have been the result of user-generated content. To protect against this, CSP allows you to specify a nonce within the <style> and <script> tags, and to add those values to the response’s header. This is a pain to handle in real life, and is most secure when generated on the fly. To make this simple, you can include a {csp-style-nonce} or {csp-script-nonce} placeholder in the tag and it will be handled for you automatically:

// Original
<script {csp-script-nonce}>
    console.log("Script won't run as it doesn't contain a nonce attribute");
</script>

// Becomes
<script nonce="Eskdikejidojdk978Ad8jf">
    console.log("Script won't run as it doesn't contain a nonce attribute");
</script>

// OR
<style {csp-style-nonce}>
    . . .
</style>

Warning

If an attacker injects a string like <script {csp-script-nonce}>, it might become the real nonce attribute with this functionality. You can customize the placeholder string with the $scriptNonceTag and $styleNonceTag properties in app/Config/ContentSecurityPolicy.php.

If you don’t like this auto replacement functionality, you can turn it off with setting $autoNonce = false in app/Config/ContentSecurityPolicy.php.

In this case, you can use the functions, csp_script_nonce() and csp_style_nonce():

// Original
<script <?= csp_script_nonce() ?>>
    console.log("Script won't run as it doesn't contain a nonce attribute");
</script>

// Becomes
<script nonce="Eskdikejidojdk978Ad8jf">
    console.log("Script won't run as it doesn't contain a nonce attribute");
</script>

// OR
<style <?= csp_style_nonce() ?>>
    . . .
</style>