Content Security Policy
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 forRefused 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.
Cypress' configurable CSP handling applies only to policies delivered via
HTTP headers, not those delivered via a <meta> tag:
experimentalCspAllowListoperates 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
nonceinjection 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:
| Value | Behavior |
|---|---|
false | (default) Strip the entire CSP header. The policy is removed completely before the response reaches the browser. |
true | Keep 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-srcchild-srcframe-srcscript-srcscript-src-elemform-action
Any other directive name is not accepted by the array. Regardless of which mode you choose, the always-stripped directives below are removed.
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):
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// false is the default; the entire CSP header is stripped
experimentalCspAllowList: false,
})
import { defineConfig } from 'cypress'
export default defineConfig({
// false is the default; the entire CSP header is stripped
experimentalCspAllowList: false,
})
Keep the header, stripping only the directives that interfere with Cypress:
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
experimentalCspAllowList: true,
})
import { defineConfig } from 'cypress'
export default defineConfig({
experimentalCspAllowList: true,
})
Keep the header and only the named directives (each must be one of the six allowable values):
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
experimentalCspAllowList: ['default-src', 'script-src', 'script-src-elem'],
})
import { defineConfig } from 'cypress'
export default defineConfig({
experimentalCspAllowList: ['default-src', 'script-src', 'script-src-elem'],
})
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 directive | Reason |
|---|---|
frame-ancestors | Prevents Cypress from loading a test application into an iframe. |
navigate-to | Affects Cypress' ability to navigate to different URLs. |
require-trusted-types-for | Might prevent Cypress from rewriting the DOM. |
sandbox | Can restrict access to script and iframe functionality. |
trusted-types | Could cause Cypress injections to be marked as untrusted. |
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-elemscript-srcdefault-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.
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:
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
experimentalCspAllowListconfiguration reference- Content Security Policy documentation on MDN