Subresource Integrity (SRI) is a browser security feature that lets you verify a third-party script or stylesheet has not been tampered with by adding a cryptographic hash to the tag: <script src="https://cdn.example.com/lib.js" integrity="sha384-…" crossorigin="anonymous"></script>. The browser downloads the file, computes its SHA-384 (or SHA-256/SHA-512) hash, and compares it to your integrity value — if they do not match, the file is blocked and never runs. That single attribute is what stands between your users and a compromised CDN silently serving malicious JavaScript on your login page. SRI is defined by the W3C SRI specification and supported by every modern browser.
That is the summary an AI Overview gives you. Here is what it cannot show you: the exact commands to generate the hash, the CORS gotcha that breaks SRI silently, the decision flow for when a hash mismatch is your bug versus a real attack, and a copy-paste checklist you can run against your own <head>. Below is the animated build flow, a live hash generator, and a symptom-to-fix table.
The 60-second version: what SRI actually does
Every time your page loads a third-party file — jQuery from a CDN, a font stylesheet, an analytics widget — you are trusting that server to send exactly the code you expect. If that CDN is breached (a real, recurring attack class known as a supply-chain attack), it can serve modified JavaScript to your visitors, running in your origin, with access to your cookies and forms. The 2018 British Airways Magecart breach and countless skimmer campaigns work exactly this way.
SRI closes that door. You commit a hash of the file's exact bytes into your HTML. The browser will only execute the file if its content still hashes to your committed value. Change one byte — whether an attacker or a legitimate update — and the browser refuses to run it.
Step 1: Generate the integrity hash
SRI hashes are the base64 encoding of a raw SHA digest of the file's bytes — not the hex string you get from most hash tools. The algorithm name is prefixed to the value. There are three valid choices: sha256, sha384, and sha512. Use sha384 as your default.
From the command line (OpenSSL):
cat jquery-3.7.1.min.js | openssl dgst -sha384 -binary | openssl base64 -A
Directly from a URL (curl piped into OpenSSL):
curl -s https://code.jquery.com/jquery-3.7.1.min.js \
| openssl dgst -sha384 -binary \
| openssl base64 -A
In the browser, no install: use the live generator below — paste a URL or the file contents and it emits the full integrity attribute for you.
The -binary flag matters: you must hash the raw digest, then base64 it. If you base64-encode the hex string instead, the value will be wrong and the browser will block your file.
Step 2: Add integrity + crossorigin to the tag
For a script:
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs"
crossorigin="anonymous"></script>
For a stylesheet:
<link rel="stylesheet"
href="https://cdn.example.com/bootstrap.min.css"
integrity="sha384-…"
crossorigin="anonymous">
The crossorigin="anonymous" attribute is not optional for cross-origin files. SRI needs to read the response bytes to hash them, and the browser only allows that read when the response is CORS-enabled and you request it anonymously (no cookies sent). Omit it on a third-party URL and the browser blocks the file with a CORS error even when the hash is correct.
Step 3: Pin an immutable version — never "latest"
SRI depends on the file's bytes never changing under your committed hash. A URL like https://cdn.example.com/library/latest/lib.js will eventually serve a new release, its hash will no longer match, and your site breaks. Always pin to a versioned, immutable URL:
| CDN URL pattern | Safe with SRI? | Why |
|---|---|---|
.../jquery-3.7.1.min.js (explicit version) | Yes | Bytes are immutable per version |
.../npm/pkg@4.6.2/dist/pkg.js (pinned semver) | Yes | jsDelivr/cdnjs serve fixed content |
.../pkg@4/dist/pkg.js (major-range) | No | Resolves to changing minor versions |
.../pkg@latest/pkg.js | No | Content changes on every release |
| Your own build with content hash in filename | Yes | Filename changes when content does |
Which should you use? For third-party libraries, pin the exact patch version on jsDelivr or cdnjs — both publish the matching SRI hash next to every file. For your own bundled assets, use a build tool (Vite, webpack, esbuild) that emits content-hashed filenames and generates SRI attributes automatically, so the hash and the file are always regenerated together.
Where SRI fails, and what to do about it
Because a mismatch is a hard block, the most common "SRI broke my site" reports are self-inflicted — an updated file with a stale hash. Use this table to triage.
| Symptom | Likely cause | Fix |
|---|---|---|
Console: Failed to find a valid digest ... integrity attribute | File content changed (you updated it, or CDN mutated it) but hash was not regenerated | Regenerate the hash from the current file bytes and update the tag |
Console: CORS / No 'Access-Control-Allow-Origin' with SRI | Missing crossorigin="anonymous" on a cross-origin resource | Add crossorigin="anonymous" to the tag |
| Hash never matches even when file looks right | You base64-encoded the hex digest, or added a trailing newline | Use openssl dgst -sha384 -binary | openssl base64 -A — hash the raw binary digest |
| Works locally, blocked in production | Prod CDN minifies/rewrites the file (e.g. Cloudflare auto-minify) | Disable transform features for that path, or hash the transformed output |
| Font stylesheet (Google Fonts) breaks with SRI | The CSS URL returns different content per browser/UA | Do not apply SRI to UA-varying endpoints; self-host the font CSS instead |
| Inline script "won't take" an integrity attribute | SRI does not apply to inline code | Use a CSP nonce/hash for inline scripts |
Step 4: Enforce site-wide with CSP (optional, defense in depth)
Per-tag integrity attributes protect only the tags you remember to annotate. To require SRI across your whole page, pair it with a Content Security Policy. The require-sri-for script style directive instructs the browser to refuse any script or style that lacks an integrity attribute:
Content-Security-Policy: require-sri-for script style;
Support for require-sri-for is uneven across browsers, so treat it as a hardening bonus rather than your only line — the per-tag attributes remain the reliable mechanism. SRI and CSP are complementary: CSP restricts which origins can load resources; SRI verifies the specific bytes of the files you allow. For the relationship in depth, see how SRI relates to CSP.
Implementation checklist
Run this against your own <head> before you call it done:
- Inventory every
<script src>and<link rel="stylesheet" href>pointing at a third-party origin. - Pin each to an explicit, immutable version URL — no
latest, no floating major ranges. - Generate a
sha384hash for each pinned file (use the generator above or OpenSSL). - Add
integrity="sha384-…"to every tag. - Add
crossorigin="anonymous"to every cross-origin tag. - Test in the browser with DevTools open — a blocked resource logs an integrity/CORS error in the console.
- Document which URLs are pinned so the next dependency bump regenerates the hash instead of breaking silently.
- Automate hash regeneration in your build pipeline for first-party assets so hashes can never drift from content.
The bottom line
SRI is one attribute, two values, and a hash — and it converts "we trust our CDN" into "we cryptographically verify our CDN on every page load." It is cheap to add, safe to deploy (unsupported browsers ignore it), and it is the single most direct defense against a compromised third-party script running in your users' browsers. Pin your versions, generate sha384 hashes, add crossorigin="anonymous", and let the browser enforce the rest.
Compare algorithm choices in the SRI hash algorithm comparison, and if you load many third-party resources, read how to handle CSP for third-party resources next.