All posts

How to generate a CSP hash (sha256) for an inline script

CentralCSP Team ·

Last update:

A CSP hash allowlists one exact inline script by the fingerprint of its content. A Content Security Policy (CSP) is an HTTP response header that tells the browser which scripts it may run, and by default it blocks inline scripts. A hash source lets you keep one specific inline block working without re-enabling all inline script.

The short version: compute a SHA-256 digest of the exact text inside the <script> tag, base64-encode it, and add it to script-src as 'sha256-...'. The browser hashes each inline script it finds and runs only the ones whose hash you listed. If you would rather skip the math, paste the snippet into our hash generator and copy the source value it returns.

New to inline handling? Why you should never use unsafe-inline in CSP explains why hashing beats 'unsafe-inline'.

What a CSP hash is

A hash source is a base64-encoded cryptographic digest of an inline script's content, written into your policy. When the browser meets an inline <script>, it computes the same digest over that script's content and checks it against the hashes in the directive. A match runs; anything else is blocked.

Because the hash is tied to the exact bytes of the script, an attacker who injects different inline code cannot match it, so the injected script never runs. That is what makes a hash a safe way to allow specific inline blocks.

The three source forms

CSP supports three digest algorithms, written with the algorithm as a prefix:

  • 'sha256-...'
  • 'sha384-...'
  • 'sha512-...'

All three are widely supported in current browsers. SHA-256 is the common choice; the longer variants produce longer values for no security benefit at typical script sizes. The value after the prefix is the base64 encoding of the raw digest, not the hex form.

The hash is over the exact content (whitespace matters)

The digest is computed over the exact text between the opening and closing tags, not the tags themselves and not any attributes. Every character counts: spaces, tabs, trailing newlines, and capitalization are all part of the content.

<script>console.log("hello");</script>

The hash for that block covers exactly console.log("hello");. Add a leading space, a trailing newline, or change a quote style and the digest changes, so the source you listed no longer matches and the browser blocks the script. Compute the hash from the final, minified bytes you actually serve, not from a prettified copy.

Compute the hash

For a single snippet, paste the inline code into the hash generator and it returns the 'sha256-...' source ready to drop into the policy, with no command line and no whitespace mistakes.

In a build step, compute it in Node.js with the built-in crypto module:

const crypto = require("crypto");

function cspHash(content) {
  const digest = crypto.createHash("sha256").update(content, "utf8").digest("base64");
  return `'sha256-${digest}'`;
}

console.log(cspHash('console.log("hello");'));

This produces the same value, computed from the exact UTF-8 bytes of the script content, so wire it into your build to keep hashes in sync as the inline code changes.

Where the hash goes

Add the source to the directive that governs the resource. For inline scripts that is script-src; for inline styles it is style-src. You can list several hashes to allow several inline blocks.

Content-Security-Policy: script-src 'self' 'sha256-q1V8...=' 'sha256-Hk9p...='

You can mix hashes with 'self' and host sources for your external files, and the hashes only ever apply to inline content. If the directive also has a nonce or a hash, the browser ignores 'unsafe-inline' in that same directive, so a leftover 'unsafe-inline' does not undo your hashes (remove it anyway).

Hash vs nonce, when to pick which

Hashes and nonces solve the same problem, allowing specific inline scripts, but suit different content.

  • Use a hash for static inline code that does not change between responses: a build-time inline bundle, a fixed analytics snippet, a small constant block. You compute the hash once at build time and it stays valid until the content changes.
  • Use a nonce for dynamic, server-rendered pages where the inline content or the set of inline scripts varies per request. A nonce is a fresh random value per response and does not depend on the script's content.

A hash needs no per-request work, which makes it ideal for static hosting and cached pages. A nonce needs the server to generate and inject a value on every response. Many sites use hashes for fixed snippets and a nonce for server-rendered blocks.

The inline-handler caveat ('unsafe-hashes')

A plain hash source allowlists inline <script> blocks. It does not cover inline event handler attributes like onclick="..." or javascript: URLs. Allowing a handler by its hash requires the separate 'unsafe-hashes' keyword, which loosens the policy and is best avoided.

<!-- Hashing this needs 'unsafe-hashes', which weakens the policy -->
<button onclick="saveForm()">Save</button>

The better fix is to remove the inline handler and bind it from a hashed or nonced script with addEventListener:

// markup: <button id="save">Save</button>
document.getElementById("save").addEventListener("click", saveForm);

That keeps the policy strict and avoids 'unsafe-hashes' entirely.

See it in your reports

When a hash does not match (often a whitespace change you missed), the browser blocks the script and reports a violation. CentralCSP collects those reports from real traffic and groups them, so a hash that drifted out of sync shows up as a recurring block instead of a silent breakage. You can start a free trial and point a Report-Only header at it while you roll out hashes.

For the full directive and keyword reference, see the CSP policy reference.

Violation rows whose blocked origin reads inline rather than a host

Frequently asked questions

Why does my CSP hash not match the script?

Almost always whitespace. The hash covers the exact text between the tags, including leading or trailing spaces and newlines. Compute it from the final served bytes, not a formatted copy, or use the hash generator on the exact snippet.

Should I use a hash or a nonce?

Use a hash for static inline scripts that rarely change, and a nonce for dynamic, server-rendered pages. Hashes need no per-request work; nonces do not depend on the script's content.

Can a CSP hash allow an inline onclick handler?

Not with a plain hash. Allowing an inline event handler by hash needs the 'unsafe-hashes' keyword, which loosens the policy. Remove the inline handler and bind it with addEventListener instead.

Sources