How to protect CDN scripts with Subresource Integrity
CentralCSP Team ·
Last update:
When you load a script from a CDN, you are trusting whoever controls that server to never serve anything but the file you expect. Subresource Integrity (SRI) removes that trust. You add a cryptographic hash of the file you expect to the <script> or <link> tag, and the browser refuses to run anything that does not match.
The short version: put an integrity hash on every third-party script and stylesheet, and pair it with crossorigin so the browser can verify it. If a CDN is compromised and starts serving altered code, the browser blocks the load instead of executing the tampered file. This post covers the exact syntax, how the browser picks which hash to check, why crossorigin is mandatory for cross-origin files, and how SRI fits next to your Content Security Policy.
What is Subresource Integrity?
SRI is a W3C specification that adds an integrity attribute to HTML elements that load external resources. The value is a hash of the content you expect. When the browser fetches the resource, it hashes the bytes it received and compares them to your value. A match runs the file. A mismatch is treated as a network error, so the resource is not loaded, applied, or executed.
That single check covers a real risk. A CDN account gets compromised, a build pipeline is poisoned, or a man-in-the-middle swaps the response, and your page quietly starts running attacker code. With SRI in place, any byte-level change to the file breaks the hash and the browser drops it.
The integrity attribute is supported on <script>, and on <link> with a rel of stylesheet, preload, or modulepreload.
How to use the integrity attribute
The value is one or more hashes. Each hash is an algorithm prefix (sha256-, sha384-, or sha512-), a hyphen, then the base64-encoded digest. Multiple hashes are separated by whitespace. SRI requires browsers to support SHA-256, SHA-384, and SHA-512; SHA-384 is a common default.
Here is a cross-origin script with an integrity hash and the crossorigin attribute it needs:
<script
src="https://cdn.example.com/script.js"
integrity="sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7"
crossorigin="anonymous"></script>A stylesheet works the same way:
<link
rel="stylesheet"
href="https://cdn.example.com/styles.css"
integrity="sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7"
crossorigin="anonymous">You generate the hash from the exact file the CDN serves. Paste the URL or file into the SRI generator, which produces the SHA-256, SHA-384, and SHA-512 values for you. SRI does not auto-update: if the CDN legitimately ships a new version of the file, you regenerate the hash, or the resource stops loading. That is the feature working as designed, not a bug.
Which hash does the browser check?
You can list more than one hash, and this is where a common misconception lives. The browser does not use the first hash, and it does not use the first algorithm it supports. It uses the strongest algorithm present.
If your integrity value contains both a SHA-256 and a SHA-384 hash, the browser selects the SHA-384 hashes and ignores the SHA-256 ones entirely. Listing a weaker hash alongside a stronger one does not weaken the check.
<script
src="https://cdn.example.com/script.js"
integrity="sha256-yourSha256Digest
sha384-yourSha384Digest"
crossorigin="anonymous"></script>In that example only the SHA-384 hash is used. This behavior is defined in the SRI specification ("get the strongest metadata from set") and matches how browsers implement it: the browser validates against the strongest algorithm you list, so SHA-512 wins over SHA-384, which wins over SHA-256.
Listing multiple hashes of the same algorithm behaves differently and is useful. The browser keeps all of them, and the resource validates if any one matches. That lets you accept more than one known-good version of a file at the same URL.
Why crossorigin is required
For a cross-origin resource, the integrity attribute does nothing on its own. You also need crossorigin, and leaving it off is the single most common reason SRI silently fails.
The reason is CORS. Without crossorigin, the browser fetches a cross-origin file in no-cors mode, and the response is opaque: your page cannot read its bytes. Browsers refuse to run SRI on a no-cors request, so the load fails. Allowing the check on opaque responses would also leak data, an attacker could brute-force cross-origin content by testing hashes against it, so CORS forces the resource owner to explicitly share the file before SRI can read it.
The crossorigin attribute has two useful values:
anonymous(or an empty value) uses CORS without sending cookies or credentials cross-origin. Use this for public CDN files.use-credentialsuses CORS and always sends credentials. Use this only when the third party requires it.
Same-origin resources are different. They are already in a sharing-permitted state, so a same-origin <script> with integrity is checked without needing crossorigin. The attribute is the cross-origin requirement.
SRI and Content Security Policy
SRI verifies the content of a file you already decided to load. A Content Security Policy (CSP) decides which files are allowed to load at all. They solve different halves of the same problem, and they work well together.
A CSP script-src directive controls the origins and inline code your page may run. SRI then guarantees the specific third-party files you allow have not been altered. If you are building a policy from scratch, how to build a strong CSP and the script-src reference cover the directives, and the CSP hashes and nonce page explains how CSP uses its own hashes (a separate mechanism from SRI, for allowing inline code; see SRI vs CSP hash for the distinction), and how to generate a CSP hash walks through producing one.
There used to be a CSP directive, require-sri-for, meant to force SRI on every script or style. Do not reach for it: it was removed from the spec and never shipped to stable browsers. It is not interoperable today. The modern direction for requiring integrity site-wide is the Integrity-Policy header, which can block scripts that lack integrity and report them as an integrity-violation. It recently became available across current Chrome, Firefox, and Safari; the guide on how to enforce SRI everywhere with Integrity-Policy walks through it. For the full attribute, hashing, and Integrity-Policy reference, see Subresource Integrity (SRI).
A quick checklist
- Add
integrityto every cross-origin<script>and<link rel="stylesheet">you control. - Always pair a cross-origin
integritywithcrossorigin="anonymous". - Prefer SHA-384 or SHA-512; if you list more than one algorithm, the browser uses the strongest.
- Regenerate the hash whenever the upstream file changes, or automate SRI hashes in your build.
SRI hardens the third-party code you already load, and a script inventory tells you what that third-party code actually is. CentralCSP builds an inventory of every script running on your pages from CSP hash reports, so you can see new or changed scripts before they become an incident. Start free to map your client-side dependencies.