script-src
script-src is the directive CSP exists for. It governs every route by which a page can execute JavaScript, and it is the directive that produces most of the violations in a typical HeaderHawk site.
It is also the directive that is hardest to get right, because an allowlist of CDN hosts is usually bypassable. The modern answer is a per-response nonce plus 'strict-dynamic', which trusts scripts loaded by scripts you already trusted and ignores the host allowlist entirely.
Syntax
Section titled “Syntax”Content-Security-Policy: script-src 'none';Content-Security-Policy: script-src <source-expression-list>;What it controls
Section titled “What it controls”- URLs loaded into
<script>elements. - Inline
<script>blocks. - Inline event handler attributes such as
onclickandonerror. eval(),Function(), andsetTimeout()/setInterval()called with a string.- WebAssembly compilation and instantiation.
- XSLT stylesheets that can trigger script execution.
<script type="speculationrules">.
Fallback
Section titled “Fallback”If script-src is absent, the browser consults default-src. The full chain is script-src → default-src.
Recommended value
Section titled “Recommended value”Content-Security-Policy: script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic'- A fresh, cryptographically random nonce per response, emitted on every
<script>you control.'strict-dynamic'then extends that trust to scripts those scripts load, which is what makes tag managers and bundlers workable without an allowlist. - Keep
'self'and anyhttps:sources in the policy for older browsers: a CSP 3 browser ignores them once'strict-dynamic'is present, and a CSP 2 browser ignores'strict-dynamic'and uses them. - A nonce must not be reused across responses, and must not be predictable. A static nonce is an allowlist of one attacker-guessable value.
Examples
Section titled “Examples”Allowed
Section titled “Allowed”An inline script carrying the response’s nonce:
<script nonce="2726c7f26c"> window.APP_CONFIG = { region: "eu-west-1" };</script>An external script, under 'strict-dynamic', injected by a script that already carried the nonce:
const s = document.createElement("script");s.src = "https://cdn.example.com/analytics.js";document.head.append(s);Blocked
Section titled “Blocked”An inline handler: hashes and nonces do not cover attributes, only elements.
<button onclick="checkout()">Buy</button>eval() and friends, unless 'unsafe-eval' is present - which defeats most of the point of the directive:
setTimeout("refresh()", 1000);Browser support
Section titled “Browser support”- Widely available across browsers since August 2016.
'strict-dynamic'needs a CSP Level 3 browser. Older browsers fall back to the host allowlist in the same policy, which is why you keep one.'unsafe-hashes'is what allows a hashed inline event handler. A plain hash source does not cover attributes.
In a HeaderHawk report
Section titled “In a HeaderHawk report”| Field | Value |
|---|---|
violatedDirective |
script-src |
effectiveDirective |
script-src-elem |
blockedUri |
https://cdn.tracker.example/tag.js, inline, eval, wasm-eval |
| Issue title | script-src-elem blocking cdn.tracker.example |
- Write
script-srcand your reports will still sayscript-src-elemorscript-src-attr. Browsers report the most specific directive that was consulted, andscript-srcis only its fallback. HeaderHawk groups on the effective directive, so the-elemand-attrviolations of onescript-srcrule arrive as two issues. blockedUri: "inline"means a<script>block or an event handler was refused, not a URL. HeaderHawk groups inline violations by a hash of the code sample, so two different inline scripts are two issues rather than one pile - but only if your policy carries'report-sample', which is what makes browsers send the sample at all.evalandwasm-evalare values ofblockedUri, not URLs; they meaneval()and WebAssembly compilation respectively.
Related directives
Section titled “Related directives”Getting reports for this directive
Section titled “Getting reports for this directive”Point your policy’s report-uri at your site’s HeaderHawk endpoint and the violations above arrive in the dashboard, grouped as described. The Quick Start sets that up in five minutes, and the integration guides cover the header syntax for each platform.