Subresource Integrity (SRI): Protecting Against Compromised CDN Resources
How Subresource Integrity works to guarantee that CDN-hosted scripts and stylesheets have not been tampered with—generating SRI hashes, applying them to script and link tags, handling multi-CDN scenarios, and combining SRI with a strict Content Security Policy.
The CDN Trust Problem
When your application loads a script from a CDN—jQuery from cdn.jsdelivr.net, a font from fonts.googleapis.com, an analytics library from cdn.mixpanel.com—you are trusting that CDN to serve exactly the file you intended to load. Every visitor to your application executes that code in their browser with full access to the DOM, cookies, and any data visible on the page.
CDNs are attractive attack targets precisely because compromising one affects every website that uses it. Historical incidents demonstrate that this is not a theoretical concern:
- The Polyfill.io CDN was purchased by a Chinese company in 2024, which then modified the polyfill.min.js to inject malicious redirects. Over 100,000 websites that loaded this CDN script became malicious traffic redirectors overnight.
- Magecart attacks have repeatedly targeted CDN-hosted JavaScript for e-commerce sites to inject payment card skimming code.
- npm package compromises where a CDN mirrors an npm package get the malicious version served to all downstream consumers.
Subresource Integrity (SRI) provides a cryptographic guarantee: the browser verifies that a CDN-hosted resource's content matches a hash you specified before executing it. If the CDN serves a different file—modified, backdoored, or replaced—the browser refuses to load it.
How SRI Works
SRI uses the integrity attribute on <script> and <link> tags. The attribute value is a hash of the file content:
<script
src="https://cdn.example.com/library.min.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous">
</script>
When the browser encounters this tag:
- It fetches the resource from the CDN
- Computes the hash of the fetched content
- Compares it against the value in the
integrityattribute - If they match: the resource is executed/applied
- If they do not match: the resource is silently blocked and an error is logged to the console
The crossorigin="anonymous" attribute is required for SRI to work on cross-origin resources. Without it, the browser cannot verify the hash because CORS restrictions prevent the content from being read.
Supported Hash Algorithms
SRI supports SHA-256, SHA-384, and SHA-512. SHA-384 is the most commonly recommended—stronger than SHA-256 with negligible performance overhead. Multiple hashes can be specified for the same resource (the browser uses the strongest supported algorithm):
<script
src="https://cdn.example.com/library.min.js"
integrity="sha256-abc123... sha384-def456..."
crossorigin="anonymous">
</script>
Multiple hash values separated by a space allow for rollover scenarios: during a CDN migration, specify the old hash and the new hash. Browsers will accept either.
Generating SRI Hashes
Using the Command Line
# Generate SHA-384 hash for a remote file
curl -s https://cdn.example.com/library.min.js | openssl dgst -sha384 -binary | openssl base64 -A
# Output: oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC
# Full integrity attribute value
echo -n "sha384-$(curl -s https://cdn.example.com/library.min.js | openssl dgst -sha384 -binary | openssl base64 -A)"
# For a local file
cat library.min.js | openssl dgst -sha384 -binary | openssl base64 -A
Using the SRI Hash Generator
The official tool at https://www.srihash.org/ accepts a URL and returns the full integrity attribute value. This is the fastest option for one-off hash generation.
Using npm for Node.js Build Pipelines
const crypto = require('crypto');
const fs = require('fs');
function generateSriHash(filePath, algorithm = 'sha384') {
const content = fs.readFileSync(filePath);
const hash = crypto.createHash(algorithm).update(content).digest('base64');
return `${algorithm}-${hash}`;
}
// Usage in a build script
const integrity = generateSriHash('./dist/bundle.js');
console.log(`integrity="${integrity}"`);
Build Tool Integration
For production applications, SRI hashes should be generated during the build process and injected into HTML templates, not calculated manually. This ensures the hash always matches the built artifact.
Webpack + webpack-subresource-integrity:
// webpack.config.js
const SriPlugin = require('webpack-subresource-integrity');
module.exports = {
output: {
crossOriginLoading: 'anonymous',
},
plugins: [
new SriPlugin({
hashFuncNames: ['sha384'],
enabled: process.env.NODE_ENV === 'production',
}),
],
};
Vite:
Vite does not natively generate SRI hashes, but the vite-plugin-sri3 plugin adds this capability:
// vite.config.ts
import { sri } from 'vite-plugin-sri3';
export default {
plugins: [sri()],
};
Next.js: For scripts loaded via <Script> with external src, you can pass the integrity prop:
<Script
src="https://cdn.example.com/analytics.min.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossOrigin="anonymous"
strategy="lazyOnload"
/>
Applying SRI to Stylesheet Links
SRI applies to CSS as well as JavaScript. CSS can be used for data exfiltration via CSS injection attacks, and compromised stylesheets can redirect users or exfiltrate form data via CSS attribute selectors and url() functions.
<link
rel="stylesheet"
href="https://cdn.example.com/bootstrap.min.css"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
crossorigin="anonymous"
>
The Update Problem: SRI and Versioning
SRI and CDN versioning must be coordinated carefully. The problem: if you pin an SRI hash to library@2.1.0, and the CDN patches the file (even for a legitimate security fix creating library@2.1.1), your SRI will block the updated file.
Solutions:
Use exact version URLs: Pin to the exact version in the CDN URL, not a floating latest or major-version alias:
<!-- BAD: CDN may update this file -->
<script src="https://cdn.example.com/jquery/latest/jquery.min.js" integrity="sha384-...">
<!-- GOOD: Exact version -->
<script src="https://cdn.example.com/jquery/3.7.1/jquery.min.js" integrity="sha384-...">
Treat SRI updates as deployments: When you deliberately update a dependency version, regenerate the SRI hash and deploy the updated HTML. This is straightforward if hash generation is part of your build process.
Automate via Dependabot or Renovate: Renovate Bot can update both the CDN URL and the SRI hash simultaneously when a new version is available.
Multi-CDN and Fallback Scenarios
Some applications configure multiple CDN sources for reliability. SRI works with fallback patterns:
<!-- Primary CDN with SRI -->
<script
id="jquery-primary"
src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"
integrity="sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs"
crossorigin="anonymous"
onerror="loadFallback()">
</script>
<script>
function loadFallback() {
// Only load fallback if primary fails SRI check or network error
const script = document.createElement('script');
script.src = '/vendor/jquery.min.js'; // Self-hosted fallback
script.integrity = 'sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs';
script.crossOrigin = 'anonymous';
document.head.appendChild(script);
}
</script>
The self-hosted fallback should also have SRI to maintain the integrity guarantee.
Combining SRI with Content Security Policy
SRI and CSP are complementary. CSP controls where scripts can load from (origin allowlisting or nonce-based control). SRI controls what a script's content must be.
For scripts that must be loaded from CDNs:
Content-Security-Policy:
script-src 'self' 'nonce-{NONCE}'
cdn.jsdelivr.net
cdnjs.cloudflare.com;
require-sri-for script style;
The require-sri-for directive (CSP Level 3, experimental) requires that all resources matching the specified types have SRI attributes. If a <script> or <link> tag is added without an integrity attribute, the browser blocks it.
This combination is powerful:
- CSP prevents loading scripts from unauthorized origins
- SRI ensures that authorized CDN scripts have not been tampered with
require-sri-forenforces that developers cannot accidentally forget to add SRI
When SRI Cannot Help
SRI verifies the integrity of files at load time. It cannot help with:
First-party scripts: If your own JavaScript bundle is served from your domain and your server is compromised, SRI is irrelevant. Your bundled assets are not typically loaded via CDN with SRI.
API responses: SRI applies to static resources. Dynamic API calls, fetch requests, and XHR are outside SRI's scope. Compromised API responses require different controls.
Inline scripts: SRI applies to externally loaded resources. Inline scripts are not covered by SRI—they are covered by CSP nonces/hashes.
Service worker interception: A compromised service worker can intercept and modify cached resources before they reach the SRI check. Service worker integrity must be managed separately.
Despite these limitations, SRI provides a meaningful, low-cost control for third-party CDN dependencies. For any externally hosted script that cannot be moved to your own domain (analytics, fonts, UI frameworks), SRI should be the default. The only additional cost is computing a hash at build time and adding a 60-character attribute to your HTML.