RoastMyOpsec

Safety

CSP Nonces vs Hashes for Script Allowlists

January 19, 2023

CSP nonces vs hashes is an allowlist technique, not a third policy language. A nonce is a per-response random value on script-src and matching script tags — browsers run those tags, not random inline XSS. A hash (sha256-…) allows a specific inline snippet whose bytes match. Both beat 'unsafe-inline' and a domain allowlist that still runs anything hosted on a CDN you listed. Marketing sites often keep unsafe-inline because GTM wants it; that is the gap report-only is for. RoastMyOpsec reads CSP quality signals on the primary HTML response. It does not execute XSS, does not check whether the nonce is unique, and does not hash your inline scripts.

The practical difference

Nonces fit server-rendered HTML: one value, many tags, new value every response (and never in a cached HTML page that outlives the nonce). Hashes fit tiny, stable inline bootstraps you rarely edit — change a space and the hash dies. Host lists (script-src https://cdn.example) allow every file on that origin, including a compromised widget.

strict-dynamic can let a nonce- or hash-trusted script load children. That is a product decision. It is not a reason to skip CSP entirely.

How to choose based on the stack

SSR or edge-injected HTML: nonces, plus hashes for any truly static inline. Static export on a CDN: hashes or external files with SRI — a baked-in nonce on a year-old HTML file is a fossil. Tag managers: prefer loading GTM as a nonce'd external file, not unsafe-inline; if the vendor still demands inline, keep Report-Only until that changes, and still enforce frame-ancestors.

TechniqueWhat it allowsFailure modeTakeaway
Nonce + matching tagsThis response's marked scriptsCached HTML with a stale nonce; nonce in a static fileDefault for dynamic HTML
sha256 hashExact inline bytesAny edit breaks the pageTiny stable snippets
Host allowlistAnything on that originOne XSS on a listed CDNWeaker; pair with SRI
unsafe-inlineClassic inline XSSThe policy stopped meaning itGraduate off it

What a URL roast cannot prove

Seeing 'nonce-' in the header is not proof the HTML uses it or that it rotates. Seeing unsafe-inline is a strong hint the policy is still in training. Pair with report-only vs enforcing and with SRI for third-party scripts.

Common mistakes

The first mistake is a static nonce in a CDN-cached HTML shell.

The second mistake is hashes plus a comment that changes in deploy.

The third mistake is nonce on scripts and unsafe-inline still on the same directive.

Free audit the URL you own

RoastMyOpsec is a defensive public-surface roast: headers, cookies, sensitive paths, and more — no exploit payloads. Start with the free audit, then open the vault if the blurred findings look expensive.

Free audit now

FAQ

Are nonces better than hashes?
For HTML that is generated per request, nonces are usually easier. Hashes win for tiny immutable inline. Neither is a substitute for enforcing CSP.
Does RoastMyOpsec verify my nonce?
No. It reads the CSP header for quality signals. It does not compare nonces to script tags or run XSS tests.
Can I keep unsafe-inline for GTM?
That is a common stall. Prefer a nonce'd loader. If you cannot yet, stay honest in Report-Only for scripts and still enforce framing.

Sources

Related guides