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-elemfalls back toscript-src, thendefault-src.script-src-attrfalls back toscript-src, thendefault-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.
| Value | Status | Description |
|---|---|---|
'none' | ✅ Good | Blocks all script. Used alone. |
'self' | ✅ Good | Scripts from your own origin only. |
| Host source | ✅ Good | A specific host such as https://cdn.example.com. |
https: | ❌ Risky | Any HTTPS origin may serve script, close to having no allowlist. |
data: | ❌ Risky | Attacker-controlled data: URLs execute as script. |
blob: | ❌ Risky | blob: URLs execute as script, an XSS vector. |
'nonce-...' | ✅ Good | Per-response random token, unique and unguessable. |
'sha256-...' | ✅ Good | Digest of an exact inline block or external file. |
'strict-dynamic' | ✅ Good | Trust flows from nonce or hash allowed scripts; host and scheme sources are ignored. |
'wasm-unsafe-eval' | ✅ Good | WebAssembly compilation only, narrower than 'unsafe-eval'. |
'report-sample' | ✅ Good | Adds the first 40 characters of blocked inline code to reports. |
'trusted-types-eval' | ✅ Good | Allows eval only with TrustedScript when Trusted Types are enforced. Recently became available across current Chrome, Firefox, and Safari. |
'unsafe-inline' | ❌ Risky | Allows every inline script, including injected ones. |
'unsafe-eval' | ❌ Risky | Allows eval(), new Function(), and string timers. |
'unsafe-hashes' | ❌ Risky | Lets hashes match inline event handlers, re-enabling that surface. |
'inline-speculation-rules' | 🧪 Experimental | Inline speculation rules scripts. Chromium-led, not on the standards track. |
'report-sha256' / 'report-sha384' / 'report-sha512' | 🧪 Experimental | Report-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'allowseval(),new Function(), and stringsetTimeout. 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 generaleval().
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-endpointPair 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-endpointCentralCSP 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.