Subresource Integrity and 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:
- Browser encounters
<script src="..." integrity="sha256-..."> - Browser fetches the resource from the CDN
- Browser reads the
integrityvalue and decodes the base64 hash - Browser computes the hash of the downloaded bytes using the specified algorithm
- 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
| Algorithm | Hash Length | Security Level |
|---|---|---|
sha256 | 256 bits | Minimum viable |
sha384 | 384 bits | Recommended |
sha512 | 512 bits | Strongest |
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
integrityattribute - 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> (stylesheet)
<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:
- The browser does not execute the resource (for
<script>) or does not apply it (for<link>stylesheets) - 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. - The page continues loading normally — SRI failures don’t crash the page
- If a local fallback exists (no
integrityon 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
- Content Security Policy — enforce security headers across your site
- Browser Fetch and XHR — understand how the browser loads resources
- Web Workers — load scripts in isolated worker contexts