CentralCSP
PoliciesContent-Security-PolicyDirectives

script-src

The CSP script-src directive controls which scripts a page can load and run. See its values, fallback chain, examples, and bypasses.

Last update:

The script-src directive in a Content Security Policy (CSP) decides which scripts a page is allowed to load and run. It governs <script> elements, inline scripts and event handlers, javascript: URLs, eval() and similar dynamic evaluation, and the scripts that workers run. If a script does not match script-src, the browser refuses to execute it. This is the most important directive for stopping cross-site scripting (XSS).

script-src is the umbrella for two finer directives, script-src-elem for <script> elements and script-src-attr for inline event handler attributes. When you set those, they take over their part of the job and script-src becomes their fallback.

A minimal safe policy for this directive:

Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'

Fallback chain

script-src falls back to default-src. If you set default-src and omit script-src, scripts are checked against default-src. If you set script-src, it fully replaces default-src for scripts.

The finer directives fall back through script-src:

  • script-src-elem falls back to script-src, then default-src.
  • script-src-attr falls back to script-src, then default-src.

So a single script-src covers both <script> elements and inline handlers unless you override one of them.

Values

script-src takes a space-separated source list, or 'none' to block all scripts.

ValueStatusDescription
'none'✅ GoodBlocks all script. Used alone.
'self'✅ GoodScripts from your own origin only.
Host source✅ GoodA specific host such as https://cdn.example.com.
https:❌ RiskyAny HTTPS origin may serve script, close to having no allowlist.
data:❌ RiskyAttacker-controlled data: URLs execute as script.
blob:❌ Riskyblob: URLs execute as script, an XSS vector.
'nonce-...'✅ GoodPer-response random token, unique and unguessable.
'sha256-...'✅ GoodDigest of an exact inline block or external file.
'strict-dynamic'✅ GoodTrust flows from nonce or hash allowed scripts; host and scheme sources are ignored.
'wasm-unsafe-eval'✅ GoodWebAssembly compilation only, narrower than 'unsafe-eval'.
'report-sample'✅ GoodAdds the first 40 characters of blocked inline code to reports.
'trusted-types-eval'✅ GoodAllows eval only with TrustedScript when Trusted Types are enforced. Recently became available across current Chrome, Firefox, and Safari.
'unsafe-inline'❌ RiskyAllows every inline script, including injected ones.
'unsafe-eval'❌ RiskyAllows eval(), new Function(), and string timers.
'unsafe-hashes'❌ RiskyLets hashes match inline event handlers, re-enabling that surface.
'inline-speculation-rules'🧪 ExperimentalInline speculation rules scripts. Chromium-led, not on the standards track.
'report-sha256' / 'report-sha384' / 'report-sha512'🧪 ExperimentalReport-only hash collection. Chromium-only.

It accepts:

  • Keyword sources: 'self', 'none', 'unsafe-inline', 'unsafe-eval', 'wasm-unsafe-eval', 'strict-dynamic', 'unsafe-hashes', 'report-sample', 'trusted-types-eval', and 'inline-speculation-rules'.
  • A nonce or hash ('nonce-...', 'sha256-...') to allow specific inline or external scripts.
  • A host source such as https://cdn.example.com.
  • A scheme source such as https:.

Two interactions are worth knowing up front. Adding a nonce or hash makes 'unsafe-inline' ignored, so older browsers that do not understand nonces still get the inline restriction. Adding 'strict-dynamic' makes host and scheme allowlist entries ignored, trust flows from a nonce or hash instead.

Examples

A nonce-based policy that allows same-origin scripts and one inline script tagged with a matching nonce:

Content-Security-Policy: script-src 'self' 'nonce-r4nd0m'

Common use

Most pages start by allowing their own origin and a small set of trusted hosts:

Content-Security-Policy:
    script-src 'self' https://cdn.example.com;
    object-src 'none';
    base-uri 'none'

The modern recommendation is a strict CSP built on a nonce or hash plus 'strict-dynamic', rather than a list of allowed hosts. A host allowlist is easy to bypass through an open redirect or a JSONP endpoint on an allowed domain, while a nonce-based policy only trusts the exact scripts you mark. See the how-to on strict-dynamic and the nonce setup guide.

Security notes

script-src is the core XSS control. An injected <script> or inline handler runs only if it matches the directive, so a tight script-src turns an injection into a blocked, reported violation instead of code execution.

  • 'unsafe-inline' defeats most of the protection: it allows any inline script, including injected ones. Remove it and switch to a nonce or hash. See why to drop unsafe-inline.
  • 'unsafe-eval' allows eval(), new Function(), and string setTimeout. Avoid it where you can; many libraries no longer need it.
  • 'wasm-unsafe-eval' is the narrow alternative that allows WebAssembly compilation only, without enabling general eval().

You can validate a policy quickly with the CSP evaluator, which flags weak script-src values before you ship them.

Known bypasses and risks

A host allowlist is the common weak point. If an allowed origin hosts a JSONP callback, an open redirect, or a copy of a permissive framework, an attacker can load script through it. 'strict-dynamic' sidesteps this by ignoring the allowlist and trusting only nonce or hash allowed scripts and what they create.

'unsafe-hashes' widens hash matching to inline event handlers and is sometimes needed for legacy markup, but it loosens the policy, prefer moving handlers into nonce-tagged script files. A wildcard such as * or a broad scheme like https: allows script from almost anywhere and should be treated as close to having no script-src at all.

Recommendation

For script-src, ship a strict nonce-based policy instead of a host allowlist, inside a complete base policy that also sets the other important directives:

Content-Security-Policy:
    default-src 'self';
    script-src 'nonce-{RANDOM}' 'strict-dynamic';
    style-src 'self';
    img-src 'self';
    font-src 'self';
    connect-src 'self';
    media-src 'self';
    manifest-src 'self';
    frame-src 'none';
    worker-src 'self';
    object-src 'none';
    base-uri 'none';
    form-action 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
    report-to csp-endpoint

Pair it with the endpoint declaration in a separate block:

Reporting-Endpoints: csp-endpoint="https://<Endpoint-ID>.report.centralcsp.com"

This is the strict CSP that the OWASP CSP cheat sheet and web.dev recommend, extended with every no-fallback directive so nothing is left implicit. 'strict-dynamic' makes host and scheme sources irrelevant, so trust comes only from the per-response nonce and propagates to the scripts your allowed code loads. Regenerate the nonce on every response and keep 'unsafe-inline' and 'unsafe-eval' out.

Reporting

When a script is blocked, the browser sends a csp-violation report naming script-src (or the resolved script-src-elem / script-src-attr) as the effective directive. Add 'report-sample' to include a short sample of the blocked code in the report, which helps you identify the offending script. Point your policy at an endpoint to collect these:

Content-Security-Policy:
    script-src 'self' 'nonce-r4nd0m';
    report-to csp-endpoint

CentralCSP aggregates these reports and builds a script inventory of everything that runs on your pages, so you can see real script-src usage before you tighten the directive.

Browser support

script-src and its core keywords, including 'strict-dynamic', 'unsafe-eval', and 'unsafe-inline', are widely supported. 'wasm-unsafe-eval' is widely supported across current browsers. 'trusted-types-eval' recently became available across current Chrome, Firefox, and Safari. 'inline-speculation-rules' is Chromium-led, available in Safari only behind a flag, and not on the standards track. The 'report-sha256' family is Chromium-only.

FAQ

Should I use a nonce or a hash in script-src?

Use a nonce for server-rendered pages whose inline scripts change per response; the server sets a fresh random token on every response and tags each trusted script with it. Use a hash for static inline scripts that never change. Both suppress 'unsafe-inline', so older browsers still get the inline restriction.

What does strict-dynamic do?

'strict-dynamic' lets trust flow from a script the policy already allowed through a nonce or hash to any further scripts that script creates, so a trusted loader can pull in its dependencies. In exchange, host and scheme allowlist entries are ignored, which removes the open-redirect and JSONP bypasses a host allowlist carries.

Does script-src stop all XSS?

No. script-src is the core control that turns an injected script into a blocked, reported violation instead of code execution, but it is not complete on its own. Pair it with object-src 'none' and base-uri 'none' to close plugin and base-tag vectors, and add Trusted Types to lock down DOM sinks.

See also

Sources

On this page