Skip to main content
Cypress App
End-to-End Only

Content Security Policy

info

What you'll learn

  • Why Cypress needs to manage your application's Content Security Policy (CSP)
  • How to recognize the signs that you need to configure CSP handling
  • What configuring CSP handling gives you, and what it cannot do
  • How to allow your CSP through with experimentalCspAllowList

Content Security Policy (CSP) is a browser security feature that restricts the resources (scripts, styles, frames, and more) that are allowed to load into a page. It is one of the most effective defenses against cross-site scripting (XSS), but it works by blocking anything the page author did not explicitly permit. That is exactly what makes it interesting for Cypress.

Why Cypress needs to manage CSP

To drive your application, Cypress injects its own <script> tags into the page and loads your app inside an iframe. A strict CSP is designed to stop precisely this kind of injection: an unmodified policy can block Cypress' scripts from executing, forbid the app from being framed, or restrict the DOM rewriting Cypress relies on. In other words, if Cypress left your CSP untouched, adding a strict policy to your application would stop Cypress from being able to test it.

To avoid this, Cypress removes CSP by default. When a policy is delivered over HTTP headers, Cypress strips both the Content-Security-Policy and Content-Security-Policy-Report-Only headers from the response before it reaches the browser, so the browser never enforces the policy during a test run. For most tests, where you are exercising application behavior rather than the policy itself, this is invisible and requires no configuration.

Signs you need to configure CSP handling

Because Cypress strips CSP by default, two different symptoms point in opposite directions:

  • Your app or its scripts are blocked even though Cypress strips headers. This usually means the policy is delivered with a <meta> tag rather than an HTTP header, or that a directive Cypress does not strip is interfering. Look for Refused to load… / Refused to execute… CSP violation messages in the browser console.
  • You actually want the policy enforced. If your goal is to verify your own CSP (for example asserting that a violation is reported, or that a forbidden resource is blocked), the default behavior works against you, because the policy you are trying to test has been removed. You will see your CSP simply not taking effect during the test.

If either of these describes you, you need to opt in to Cypress sending the CSP header through to the browser.

What configuring CSP handling gives you

Opting in lets you run your tests against a real, enforced policy instead of no policy at all. This is valuable when:

  • You are testing the CSP implementation itself (reporting, blocking, allowed sources).
  • Your application's behavior legitimately depends on CSP being present.
  • You want your test environment to mirror production as closely as possible.

The trade-off is that Cypress still has to keep itself working, so the enforced policy is a modified version of yours rather than a byte-for-byte copy. See Always-stripped directives and Nonce injection for exactly what changes.

How CSP is delivered

There are two ways to deliver a CSP, and Cypress treats them very differently:

  • HTTP header: configurable, and the only form Cypress can allow through for testing.
  • Meta tag: supported automatically, but not configurable.

Cypress functions with the <meta> tag implementation without any configuration required. This works because Cypress loads its <script> tags into your application before any <meta> tag is parsed, so the CSP directives are never applied to the script loaded by Cypress. This relies on that ordering rather than on any modification of the policy itself.

caution

Cypress' configurable CSP handling applies only to policies delivered via HTTP headers, not those delivered via a <meta> tag:

  • experimentalCspAllowList operates exclusively on HTTP headers. It does nothing for a meta-tag CSP, so you cannot use the allow list to test a meta-delivered policy.
  • The nonce injection described below likewise applies only to headers, never to <meta> tags. The "scripts are loaded before the <meta> tag is parsed" ordering is the only mechanism that makes meta-tag CSP work with Cypress.

Allowing the header through with experimentalCspAllowList

By default Cypress strips the CSP header entirely. To keep it, so the browser enforces your policy during the test, configure the experimentalCspAllowList option. It accepts three kinds of value:

ValueBehavior
false(default) Strip the entire CSP header. The policy is removed completely before the response reaches the browser.
trueKeep the header, stripping only the directives that would interfere with Cypress (see below).
string[]Keep the header and the named directives, stripping everything else that Cypress would otherwise allow.

When using the array form, the values are validated and must be drawn from exactly these six allowable directives:

  • default-src
  • child-src
  • frame-src
  • script-src
  • script-src-elem
  • form-action

Any other directive name is not accepted by the array. Regardless of which mode you choose, the always-stripped directives below are removed.

caution

experimentalCspAllowList cannot be changed at runtime and cannot be overridden per test. Calling Cypress.config('experimentalCspAllowList', ...) from within a spec has no effect, and there is no per-test override. Changing the value requires restarting the Cypress server (for example, by stopping and relaunching cypress open or cypress run) for the new setting to take effect.

Configuration examples

Strip the entire CSP header (the default, equivalent to omitting the option):

const { defineConfig } = require('cypress')

module.exports = defineConfig({
// false is the default; the entire CSP header is stripped
experimentalCspAllowList: false,
})

Keep the header, stripping only the directives that interfere with Cypress:

const { defineConfig } = require('cypress')

module.exports = defineConfig({
experimentalCspAllowList: true,
})

Keep the header and only the named directives (each must be one of the six allowable values):

const { defineConfig } = require('cypress')

module.exports = defineConfig({
experimentalCspAllowList: ['default-src', 'script-src', 'script-src-elem'],
})
caution

Even with experimentalCspAllowList enabled, the CSP that reaches the browser is not byte-identical to your application's original policy. Cypress mutates three directives, as described in Nonce injection below. If your test asserts on the exact header value, account for this modification.

Always-stripped directives

Regardless of your experimentalCspAllowList configuration, Cypress always removes the following five directives from allowed CSP headers, because leaving them in place would prevent Cypress from functioning. They cannot be allowed through the allow list:

Stripped directiveReason
frame-ancestorsPrevents Cypress from loading a test application into an iframe.
navigate-toAffects Cypress' ability to navigate to different URLs.
require-trusted-types-forMight prevent Cypress from rewriting the DOM.
sandboxCan restrict access to script and iframe functionality.
trusted-typesCould cause Cypress injections to be marked as untrusted.
caution

This is a hard limitation: because these directives are stripped unconditionally and cannot be re-enabled, you can never test the behavior of these five directives using Cypress. If verifying them is a requirement, you will need to do so outside of Cypress.

If your application relies on any of these directives, be aware they will not be present in the policy delivered to the browser during a Cypress test run. See Strip Minimum CSP Directives for the full breakdown.

Nonce injection

To allow its injected <script> tags to execute under your policy, Cypress generates a random nonce for each response and appends a 'nonce-<value>' source to the following directives of every allowed CSP header:

  • script-src-elem
  • script-src
  • default-src

A directive is only modified if it is present in the policy and allowed through your experimentalCspAllowList configuration. The same nonce is also added to the <script> tags Cypress injects, so the browser permits them to run.

caution

Because of this, the CSP that reaches the browser is not byte-identical to your application's original policy. If you are verifying your application's CSP implementation, be aware that these three directives are modified by Cypress and the delivered header will differ from the one your server originally sent.

Nonce injection applies only to CSP delivered via HTTP headers; it is never applied to a <meta> tag policy.

Interaction with code-modifying options

Cypress options that rewrite your application's HTML or JavaScript can conflict with CSP directives that pin scripts by hash (for example a script-src value containing a 'sha256-...' source). Because the hash is computed against the original code, any rewrite changes the bytes the browser hashes and the policy will reject the modified script.

This affects the following options when combined with experimentalCspAllowList:

caution

If you allow hash-based directives through experimentalCspAllowList while any of these code-modifying options are enabled, you can hit a mismatch between the original hashed value in the directive and the rewritten HTML or JS. If you see scripts being blocked, disable the code-modifying option or remove the hash-based directive from your allow list. See Experimental CSP Allow List for more detail.

See also