Back to all articles
Implement

CSP Hash Sources: How to Allow Inline Scripts Without unsafe-inline

A CSP hash source pins a single inline script or style by its cryptographic fingerprint. Use it when you cannot inject a nonce server-side.

By CSPify Team 6 min read

Hashes pin specific inline content. Nonces are better when you can render server-side.

  • Compute sha256/sha384/sha512 of inline scripts and styles.
  • Wire hash sources into script-src and style-src.
  • Avoid unsafe-hashes unless you know exactly why.
  • Validate hash-based CSPs in report-only mode before enforcement.

Implementing a csp hash source is one of the most effective ways to secure your application against Cross-Site Scripting (XSS) without breaking essential functionality. When you are building a secure Content Security Policy, you often face a difficult choice: how do you allow legitimate inline scripts to execute without resorting to the dangerous 'unsafe-inline' directive? The answer lies in cryptographic hashes. By calculating the exact fingerprint of your inline script and adding it to your policy, you instruct the browser to execute only that specific block of code.

If you are just getting started, you can use a CSP generator to scaffold your initial policy. However, understanding the trade-offs between hashes, nonces, and 'unsafe-inline' is critical. While 'unsafe-inline' completely disables inline script protection, both hashes and nonces maintain strict security. Hashes are ideal for static, unchanging scripts, whereas nonces are dynamically generated per request. In this guide, we will explore exactly how to compute, apply, and troubleshoot CSP hash sources, ensuring your inline scripts run safely.

What a CSP hash source is

A CSP hash source is a cryptographic digest of the exact contents of an inline <script> or <style> tag. Instead of telling the browser “allow all inline scripts,” you are telling it “allow the inline script that perfectly matches this specific fingerprint.” If an attacker tries to inject a malicious script, its contents will be different, the hash will not match, and the browser will block its execution.

According to the W3C CSP3 hash-source spec section, hash sources provide a robust mechanism for locking down inline content.

'sha256-…', 'sha384-…', 'sha512-…'

When you define a hash source in your CSP header, it consists of two parts: the algorithm used to generate the hash, and the base64-encoded digest itself. The supported algorithms are SHA-256, SHA-384, and SHA-512.

A typical hash source looks like this: 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8='.

For the vast majority of web applications, SHA-256 is the standard choice. It provides excellent cryptographic security while keeping the length of your CSP header manageable. SHA-384 and SHA-512 are fully supported by modern browsers if your organization’s security policies require stronger algorithms.

What the browser actually compares

It is crucial to understand exactly what the browser hashes when it evaluates an inline script. The browser computes the hash of the text content inside the <script> or <style> tags. This means:

  • It includes all whitespace, spaces, tabs, and newlines exactly as they appear in the HTML document.
  • It is case-sensitive.
  • It does not include the <script> or </script> tags themselves.

If your build process minifies the HTML or alters the whitespace inside the script block after you have computed the hash, the browser’s computed hash will change, and the script will be blocked. The hash must match the exact byte sequence delivered to the client.

When to use a hash instead of a nonce

Both hashes and nonces are secure alternatives to 'unsafe-inline', but they serve different architectural needs. A CSP nonce is a random, single-use token generated by the server for every single HTTP response. A hash is a static fingerprint of the script’s contents.

Static pages with stable inline scripts

If you are serving a purely static website (like a JAMstack site, a documentation portal, or a standard HTML page) and you have a small, unchanging inline script—such as a theme toggle or a basic analytics initialization snippet—a hash is the perfect solution. Because the script never changes, the hash never changes. You can compute it once and hardcode it into your CSP header or meta tag.

Build-time inlined critical CSS / JS

Performance optimization often involves inlining “critical” CSS or JavaScript directly into the <head> of the document to improve First Contentful Paint (FCP) and avoid additional network requests. If this inlining happens during a build step (e.g., using Webpack, Vite, or Astro), you can write a script to compute the hashes of these inline blocks during the build and automatically inject them into your Content Security Policy.

No server-side rendering layer to inject a nonce

The biggest limitation of a nonce is that it requires a dynamic server-side rendering (SSR) layer. Your server must generate a cryptographically secure random string for every request, inject it into the CSP header, and inject the exact same string into the nonce="..." attribute of every inline script.

If your application is served from a CDN, an S3 bucket, or a static file host without an edge compute layer, you cannot use nonces effectively. In these architectures, CSP hashes are your best and most secure option for allowing inline scripts.

How to compute a CSP hash

There are several ways to compute the correct base64-encoded SHA-256 hash for your inline scripts. Here are the three most common methods used by developers.

From the command line (openssl)

If you have your script content saved in a file (or if you pipe it directly), you can use the openssl command-line tool. Remember that the hash must be computed on the exact content, without any trailing newlines added by your text editor.

To compute the hash of a file named script.js:

openssl dgst -sha256 -binary script.js | openssl base64

If you want to hash a direct string, use echo -n to prevent a trailing newline from altering the hash:

echo -n "console.log('Hello, CSP!');" | openssl dgst -sha256 -binary | openssl base64

From Node (crypto.createHash)

If you are integrating CSP generation into a Node.js build script, you can use the built-in crypto module to generate the hash programmatically.

const crypto = require('crypto');

const scriptContent = "console.log('Hello, CSP!');";
const hash = crypto.createHash('sha256').update(scriptContent).digest('base64');

console.log(`'sha256-${hash}'`);

This approach is highly recommended for automated build pipelines, ensuring your CSP header is always in sync with your bundled HTML.

From the browser DevTools console (using the violation report)

Perhaps the easiest way to find the correct hash during development is to let the browser do the work for you. If you deploy an inline script without 'unsafe-inline' and without a valid hash, the browser will block it and generate a CSP violation error in the DevTools console.

Modern browsers (like Chrome and Firefox) are incredibly helpful: the error message will often include the exact hash you need to add to your policy. It will look something like this:

Refused to execute inline script because it violates the following Content Security Policy directive: “script-src ‘self’”. Either the ‘unsafe-inline’ keyword, a hash (‘sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=’), or a nonce (‘nonce-…’) is required to enable inline execution.

You can simply copy 'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=' and paste it directly into your CSP header.

Wiring hash sources into your CSP

Once you have computed the hash, you need to add it to the appropriate directive in your Content Security Policy—typically script-src for JavaScript. For more details on the directive syntax, refer to the MDN script-src hash reference.

Here is a complete code example showing the HTTP header and the matching HTML document.

The CSP Header:

Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=';

The matching HTML:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>CSP Hash Example</title>
</head>
<body>
  <h1>Hello World</h1>
  
  <!-- This script will execute because its exact hash is in the CSP header -->
  <script>console.log('Hello, CSP!');</script>
</body>
</html>

If even a single space is added inside the <script> tag, the hash will change, and the browser will block the execution.

The unsafe-hashes trap

When developers start using CSP hashes, they often run into a frustrating limitation: hashes work for inline <script> blocks, but they do not work for inline event handlers (like <button onclick="doSomething()">) or inline style attributes (like <div style="color: red;">).

To “fix” this, developers sometimes discover the 'unsafe-hashes' keyword. Before you use it, you should run your policy through a CSP evaluator to understand the risks.

What it actually does (event handler attributes only)

The 'unsafe-hashes' directive instructs the browser to apply your defined hash sources to inline event handlers and inline style attributes, not just full <script> or <style> blocks.

For example, if you have <button onclick="alert(1)">, you could compute the hash of the string alert(1), add it to your script-src, and then add 'unsafe-hashes' to allow that specific onclick handler to run.

Why you almost never want it

You should almost never use 'unsafe-hashes'. It is named “unsafe” for a reason.

Relying on inline event handlers (onclick, onmouseover, etc.) is an outdated web development practice that mixes logic with markup. It makes your code harder to maintain and significantly complicates your Content Security Policy. If you have hundreds of different inline event handlers across your application, you will need to compute and maintain hundreds of separate hashes in your CSP header, which quickly becomes unmanageable and bloats your HTTP response size.

More importantly, it leaves your application vulnerable. If an attacker can inject HTML into your page, they might be able to reuse one of your approved event handler hashes in a different context to execute malicious actions.

Instead of using 'unsafe-hashes', the secure and modern approach is to refactor your code. Remove inline event handlers and attach event listeners securely in your external JavaScript files using addEventListener.

Hash sources for style-src

Everything we have discussed regarding script-src applies equally to CSS and the style-src directive. If you have critical inline CSS in your document <head>, you can compute its SHA-256 hash and add it to your policy.

Content-Security-Policy: default-src 'self'; style-src 'self' 'sha256-xyz123...';
<style>
  body { background-color: #f4f4f4; }
  .critical-text { font-weight: bold; }
</style>

Just like with scripts, the hash must perfectly match the text content inside the <style> tags. Be extremely careful with CSS minifiers that might alter the content after the hash has been generated.

Frequently asked questions

What is a CSP hash source?
A CSP hash source is a cryptographic digest (like SHA-256) of an inline script or style’s exact contents. When included in a Content Security Policy, it tells the browser that this specific inline block is safe to execute, without opening the door to all inline scripts via 'unsafe-inline'.

How do I generate a SHA-256 for my inline script?
You can generate a SHA-256 hash using the command line (e.g., openssl dgst -sha256 -binary script.js | openssl base64), Node.js crypto module, or simply by looking at the CSP violation report in your browser’s DevTools console, which often computes the exact hash you need.

Should I use sha256, sha384, or sha512?
SHA-256 is the most widely used and perfectly sufficient for CSP hash sources. SHA-384 and SHA-512 are also supported by modern browsers and offer higher cryptographic strength, but they result in longer strings in your CSP header. For most practical web security use cases, sha256 is the standard choice.

Can I use CSP hashes for style-src too?
Yes. CSP hashes can be applied to the style-src directive to allow specific inline <style> blocks. However, they do not apply to inline style attributes (e.g., style="color: red;") on individual HTML elements unless you use the unsafe-hashes directive, which is generally discouraged.

What does 'unsafe-hashes' mean?
The 'unsafe-hashes' directive allows you to use CSP hashes to approve inline event handlers (like onclick) and inline style attributes. It is considered unsafe because it encourages mixing logic and markup, which can lead to complex and hard-to-maintain security policies, and leaves you vulnerable to certain types of DOM-based attacks.


Implementing CSP hashes is a powerful way to secure your static and build-time inline scripts without compromising on security. However, managing these hashes, ensuring they don’t break during deployments, and monitoring for unexpected blocks requires visibility.

Try CSPify free to safely test your hash-based policies in report-only mode. Watch exactly what your policy blocks in production before you enforce it, ensuring your inline scripts run securely without breaking your site.

Recommended next step

Validate your hash-based CSP in production

Start free and watch what your hash-based policy blocks before you enforce it.