Subresource Integrity and Supply Chain

· 5 min read · Updated March 31, 2026 · intermediate
security javascript browser supply-chain

What Is Subresource Integrity?

Subresource Integrity (SRI) is a W3C security feature that lets browsers verify that resources loaded from third-party origins haven’t been tampered with. You attach a cryptographic hash to an HTML element, and the browser recalculates that hash on the downloaded resource — blocking it if the values don’t match.

SRI guards against supply chain attacks. If a CDN gets compromised and a malicious version of a library gets served, sites using SRI will refuse to load it.

How the integrity Attribute Works

The integrity attribute holds a base64-encoded cryptographic hash prefixed by the algorithm:

<script
  src="https://cdn.example.com/library.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxyZrx..."
  crossorigin="anonymous"
></script>

Format: <algorithm>-<base64-hash>

Supported algorithms: sha256, sha384, sha512.

Here’s the step-by-step flow:

  1. Browser encounters <script src="..." integrity="sha256-...">
  2. Browser fetches the resource from the CDN
  3. Browser reads the integrity value and decodes the base64 hash
  4. Browser computes the hash of the downloaded bytes using the specified algorithm
  5. Computed hash matches stored hash? The resource executes. Mismatch? The resource is blocked.

Why crossorigin Is Required

The crossorigin="anonymous" attribute is not optional for cross-origin resources. Without it, the browser may receive an opaque response — one where the body cannot be read. SRI validation requires reading the raw response body to compute the hash.

Even for publicly accessible CDN files, crossorigin="anonymous" triggers a CORS-aware fetch, which is what enables hash verification.

<!-- SRI validation may silently fail without crossorigin -->
<script src="https://cdn.example.com/lib.js" integrity="sha384-..."></script>

<!-- Correct — enables body reading for hash verification -->
<script src="https://cdn.example.com/lib.js" integrity="sha384-..." crossorigin="anonymous"></script>

Generating SRI Hashes

SHA-256 / SHA-384 / SHA-512

AlgorithmHash LengthSecurity Level
sha256256 bitsMinimum viable
sha384384 bitsRecommended
sha512512 bitsStrongest

SHA-384 is the recommended balance of security and performance.

CLI (Linux/macOS)

# SHA-384
shasum -a 384 library.js | awk '{print "sha384-" $1}' | base64

Node.js

const crypto = require('crypto');
const fs = require('fs');

function generateSRIHash(filePath, algorithm = 'sha384') {
  const fileBuffer = fs.readFileSync(filePath);
  const hash = crypto.createHash(algorithm).update(fileBuffer).digest('base64');
  return `${algorithm}-${hash}`;
}

const sri = generateSRIHash('./node_modules/lodash/lodash.min.js', 'sha384');
console.log(sri);
// Output: sha384-RXVoVUXHKy5JVeEymv...

Fetch a URL and Generate the Hash

// fetch-and-hash.js
const crypto = require('crypto');
const https = require('https');

const url = 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js';
const algorithm = 'sha384';

https.get(url, (res) => {
  const chunks = [];
  res.on('data', chunk => chunks.push(chunk));
  res.on('end', () => {
    const buffer = Buffer.concat(chunks);
    const hash = crypto.createHash(algorithm).update(buffer).digest('base64');
    console.log(`${algorithm}-${hash}`);
  });
}).on('error', err => console.error('Fetch error:', err));

Online Tools

  • srihash.org — enter a URL, it fetches the resource and returns the integrity attribute
  • Online SRI generators can compute hashes from pasted content or URLs

Verifying a File Against an SRI Hash

// verify-sri.js
const crypto = require('crypto');
const fs = require('fs');

function verifySRI(filePath, expectedSRI) {
  const [algorithm, expectedBase64] = expectedSRI.split('-');
  const fileBuffer = fs.readFileSync(filePath);
  const actualHash = crypto.createHash(algorithm).update(fileBuffer).digest('base64');
  const matches = crypto.timingSafeEqual(
    Buffer.from(expectedBase64, 'base64'),
    Buffer.from(actualHash, 'base64')
  );
  console.log(`Expected: ${expectedSRI}`);
  console.log(`Computed: ${algorithm}-${actualHash}`);
  console.log(`Match: ${matches ? 'YES — integrity verified' : 'NO — file tampered!'}`);
  return matches;
}

verifySRI('./vendor/library.min.js', 'sha384-RXVoVUXHKy5JVeEymv...');

Use crypto.timingSafeEqual to prevent timing attacks when comparing hashes.

Applying SRI to Different Elements

<script> (most common)

<script
  src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"
  integrity="sha384-HI0WcJGdMuDmv4qPFHl4VqDkmCU6RcEFcA=="
  crossorigin="anonymous"
></script>
<link
  rel="stylesheet"
  href="https://cdn.example.com/styles.css"
  integrity="sha384-HASH_HERE"
  crossorigin="anonymous"
/>

<img> and other elements

SRI also works on <img>, <video>, <audio>, <source>, and <track>:

<img
  src="https://cdn.example.com/image.png"
  integrity="sha384-HASH_HERE"
  crossorigin="anonymous"
  alt="..."
/>

A failed integrity check on an image results in the image not rendering — no broken icon, just empty space.

What Happens When an Integrity Check Fails

SRI is fail-closed. When the computed hash doesn’t match:

  1. The browser does not execute the resource (for <script>) or does not apply it (for <link> stylesheets)
  2. A console error is thrown:
    Failed to find a valid digest in the 'integrity' attribute
    for resource 'https://cdn.example.com/lib.js'
    The resource has been blocked.
  3. The page continues loading normally — SRI failures don’t crash the page
  4. If a local fallback exists (no integrity on a local copy), that loads instead

Important: SRI failures happen at the network layer. JavaScript cannot catch them with try/catch — there’s no DOM exception thrown that you can intercept.

Real-World Supply Chain Incidents

The event-stream Incident (2018)

A malicious actor created the event-stream npm package and added a dependency on flatmap-stream, which contained code designed to exfiltrate cryptocurrency wallet private keys. The attacker specifically targeted the copay Bitcoin wallet app.

Applications loading event-stream from a CDN without an integrity hash had no defense. Projects using SRI were protected because the tampered version’s hash would not match.

The npm Package Hijacking (2022)

Attackers compromised npm accounts of maintainers for widely-used packages (colors, faker, web-resource-inliner and others) via phishing. Malicious updates printed obscene ASCII art or bricked applications. These packages had billions of weekly downloads.

SRI on CDN-served copies would have blocked the malicious versions from executing.

Common SRI Mistakes to Avoid

Do NOT use MD5 or SHA-1. These are cryptographically broken for integrity purposes. Only sha256, sha384, and sha512 are valid per the SRI spec.

Do NOT omit crossorigin="anonymous" on cross-origin resources. The integrity check will silently fail or behave inconsistently across browsers.

Do NOT include query strings in the URL if the CDN ignores them. A ?v=1.0 may be stripped, changing the hash. Pin to specific versioned URLs instead (e.g., @4.17.21).

Do NOT use integrity with data: URIs. Browsers block these combinations for security reasons.

Do NOT assume SRI works for all resource types in all browsers. While <script>, <link>, and <img> have solid support, some elements like <use> for SVG sprites have inconsistent behavior.

Do NOT use SRI as your only defense. It is defense-in-depth. Combine it with CDN access controls and Content Security Policy require-sri-for directives.

SRI and Content Security Policy

You can enforce SRI site-wide using CSP:

<meta http-equiv="Content-Security-Policy" content="require-sri-for script;">

This tells the browser to refuse loading any script without a valid integrity attribute. You can also extend it to stylesheets: require-sri-for script style;

CSP require-sri-for combined with SRI hashes gives you layered protection against supply chain attacks.

See Also