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.
| Technique | What it allows | Failure mode | Takeaway |
|---|---|---|---|
| Nonce + matching tags | This response's marked scripts | Cached HTML with a stale nonce; nonce in a static file | Default for dynamic HTML |
| sha256 hash | Exact inline bytes | Any edit breaks the page | Tiny stable snippets |
| Host allowlist | Anything on that origin | One XSS on a listed CDN | Weaker; pair with SRI |
| unsafe-inline | Classic inline XSS | The policy stopped meaning it | Graduate 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 nowFAQ
- 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.