Skip to content

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.

Content-Security-Policy: script-src 'none';
Content-Security-Policy: script-src <source-expression-list>;
  • URLs loaded into <script> elements.
  • Inline <script> blocks.
  • Inline event handler attributes such as onclick and onerror.
  • eval(), Function(), and setTimeout() / setInterval() called with a string.
  • WebAssembly compilation and instantiation.
  • XSLT stylesheets that can trigger script execution.
  • <script type="speculationrules">.

If script-src is absent, the browser consults default-src. The full chain is script-srcdefault-src.

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 any https: 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.

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);

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);
  • 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.
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-src and your reports will still say script-src-elem or script-src-attr. Browsers report the most specific directive that was consulted, and script-src is only its fallback. HeaderHawk groups on the effective directive, so the -elem and -attr violations of one script-src rule 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.
  • eval and wasm-eval are values of blockedUri, not URLs; they mean eval() and WebAssembly compilation respectively.

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.