Subresource Integrity (SRI) is a browser security feature that lets you attach a cryptographic hash to a <script> or <link> tag so the browser can verify a file was not altered before it runs it. Standardized by the W3C in 2016, SRI works by having the browser compute a SHA-256, SHA-384, or SHA-512 hash of every downloaded resource and compare it against the value in the integrity attribute. If the hashes match, the code executes. If a single byte differs — because a CDN was compromised, a package was hijacked, or a proxy injected something — the browser blocks the resource entirely. It closes the gap between "the file I tested" and "the file my users actually receive."
That is the summary an AI Overview can give you. What it cannot show you is how the check flows byte by byte, why a missing crossorigin attribute silently breaks it, and which real supply-chain attacks it would have stopped. The rest of this guide walks through the mechanism, the exact syntax, the failure modes that trip people up, and a copy-paste workflow — with a diagram and a decision table you can act on.
The problem SRI solves
When you load a library from a CDN — jQuery, a font loader, an analytics snippet, a polyfill service — you are trusting that whoever controls that server sends the same code today that they sent when you tested it. That trust is the attack surface. If the CDN is breached, a maintainer's account is hijacked, or a domain quietly changes hands, the attacker can serve modified JavaScript to every visitor on every site that embeds it. Your own servers stay untouched, your code review passes, and the malicious payload runs with full access to your page — reading form fields, stealing session cookies, skimming credit cards.
This is not hypothetical. The 2018 British Airways breach (Magecart) skimmed 380,000 payment cards through a modified script. The June 2024 polyfill.io incident injected malware into thousands of sites after the domain was sold. In both cases, SRI would have caught the altered bytes and refused to run them.
How the integrity check works
SRI inserts a verification step between "download the file" and "run the file." The browser never trusts the source blindly — it re-derives the hash locally and compares.
The critical property: the hash is derived from the file's content, not its URL or filename. An attacker can serve their payload from the exact same address, and it still fails, because the bytes are different. There is no way to forge a SHA-384 collision that also does something useful — that is the whole security argument, and it rests on the same cryptographic hash function properties that underpin TLS and digital signatures.
The syntax
A protected script tag has two attributes beyond src:
<script
src="https://cdn.example.com/library@3.6.0/dist/lib.min.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"
></script>
For a stylesheet, it is identical but on the <link>:
<link
rel="stylesheet"
href="https://cdn.example.com/theme@2.1.0/style.css"
integrity="sha384-9ndCyUa6mg8Z6z9Y9j0nsz3H1r2..."
crossorigin="anonymous"
/>
The integrity value is <algorithm>-<base64-encoded-digest>. You can supply several, space-separated, and the browser picks the strongest it supports — useful during migrations:
integrity="sha256-abc123... sha384-def456..."
Generating the hash
Never copy a hash from a random website. Generate it yourself from the exact file you intend to pin:
# From a local file
cat lib.min.js | openssl dgst -sha384 -binary | openssl base64 -A
# Straight from the CDN URL
curl -s https://cdn.example.com/library@3.6.0/dist/lib.min.js \
| openssl dgst -sha384 -binary | openssl base64 -A
Prefix the output with sha384- and drop it into the tag. If you would rather paste a URL and copy a finished tag, the SRI Hash Generator does exactly that. Whatever you use, pin an immutable, versioned URL (library@3.6.0, not library@latest) — a "latest" URL is guaranteed to break your hash the next time upstream ships a release.
SRI vs. the tools people confuse it with
SRI is one layer. It does not replace transport security or origin policy — it complements them. Here is where each fits.
| Mechanism | Protects against | What it verifies | When to use it |
|---|---|---|---|
| SRI | A trusted origin serving altered content | The exact bytes of a specific file match a known hash | Every third-party <script> and <link> you can pin to a version |
| TLS / HTTPS | Tampering in transit between server and browser | The connection is encrypted and the server's certificate is valid | Always — but it trusts the server itself completely |
| Content Security Policy | Loading from unapproved origins; inline-script injection | Which domains may serve resources at all | Alongside SRI, to control where code can come from |
| CORS | Unauthorized cross-origin reads | Whether an origin is allowed to read a response | Required plumbing so SRI can even hash cross-origin files |
The short version: TLS trusts the server, CSP trusts the origin list, and SRI trusts nothing but the hash. A CDN with valid HTTPS and an allowed CSP origin can still serve you malware — only SRI catches that. For the full relationship between the two policy layers, see how SRI relates to Content Security Policy.
The failure modes that trip people up
SRI is simple until it silently stops working. Three things break it in the field:
- Missing or wrong
crossorigin. For any cross-origin file you must setcrossorigin="anonymous", and the CDN must return anAccess-Control-Allow-Originheader. Without CORS the browser cannot read the response body to hash it, so the check fails and the resource is blocked — even though the file is perfectly fine. This is the number-one "SRI mysteriously broke my site" cause. - Pinning a mutable URL. If you point at
@latestor an un-versioned path, the upstream file changes, the hash no longer matches, and the resource vanishes with only a console error. Always pin an immutable, versioned URL and update the hash deliberately when you upgrade. - Regenerating on upgrade. SRI has no auto-update. Bumping a library version without regenerating the hash means the new file fails the old check. Bake hash regeneration into your dependency-update process.
Because a mismatch fails closed — the resource is dropped, not run — a broken hash can take out critical functionality with no visible error to end users. Test in staging after every dependency bump, and watch the browser console for Failed to find a valid digest messages.
What SRI does not cover
SRI is narrow by design. It applies reliably to <script> and <link rel="stylesheet"> (plus preload and modulepreload) — not images, fonts, iframes, or fetch() calls. It only helps for static files with a stable hash; a script that legitimately changes on every request cannot be pinned. And it verifies integrity, not intent: a hash confirms the file is the one you approved, but if you approved a malicious library, SRI faithfully runs it. It defends the delivery path — not your dependency choices. Pair it with dependency auditing and a strong supply-chain security posture.
Bottom line
If your site loads any JavaScript or CSS from a CDN or third party, add SRI to those tags. It costs a hash and a crossorigin attribute, adds no measurable latency, and turns a compromised CDN from a full page takeover into a blocked resource and a console error. The maintenance discipline — pin versions, regenerate on upgrade, keep CORS correct — is the price, and it is far cheaper than a Magecart skimmer running on your checkout page.