Subresource Integrity for External Scripts - Make the Browser Verify the Bytes
A single external <script> tag can look almost trivial. Yet it asks every visitor's browser to download code from another server and execute it inside the page. HTTPS protects the trip to that server, but what checks that the returned file is the exact version the site owner expected?
Subresource Integrity (SRI) gives the browser an expected cryptographic digest for a resource. If the downloaded bytes do not match, the browser refuses to use them. This is a narrow control rather than a complete supply-chain defense, but that narrowness is useful: it answers one concrete question before an external script runs or a stylesheet is applied.
The trust hidden inside an external resource
Consider a page that includes a library from a content delivery network:
<script src="https://cdn.example.com/library-4.2.1.min.js"></script>
The page delegates more than file delivery. JavaScript loaded this way runs in the page's context. The OWASP Third Party JavaScript Management Cheat Sheet highlights the resulting loss of control: third-party code can change outside the site's own release process, execute code in the client, and gain access to information available in the page.
TLS is still essential. It helps the browser communicate with the intended server without an intermediary casually rewriting the response. However, the W3C SRI Recommendation draws an important distinction: secure transport authenticates the connection, while SRI pins the expected content. If the CDN itself, its deployment process, or the file behind a mutable URL changes, a valid HTTPS connection can faithfully deliver bytes the website did not review.
What the browser actually verifies
An SRI value contains a hash algorithm name and a base64-encoded digest. The stable W3C Recommendation specifies SHA-256, SHA-384, and SHA-512 for this purpose. A common SHA-384 value has this shape:
sha384-aZEZqsD0hQTrQxOOiN+3hK9zrIM0coHjnIZM4RkuhmT3K0qz+sJn2b0NZNhsunyB
The digest is not encryption, a password, or proof of who wrote the file. It is a compact result calculated from the exact bytes. The page declares the expected result in an integrity attribute. The browser downloads the resource, calculates its digest, and compares the values before executing the script or applying the stylesheet. If they differ, the browser treats the load as a network error.
That failure is intentional. SRI changes an unnoticed content change into a blocked resource. It improves integrity at the cost of availability: a changed script may stop a feature, and a changed stylesheet may leave a page unstyled. The control is valuable only when the site also has a deliberate update and failure-handling process.
Generate the digest from the bytes you will deploy
Suppose library-4.2.1.min.js is the reviewed file. The MDN SRI guide documents an OpenSSL pipeline for generating a SHA-384 digest:
openssl dgst -sha384 -binary library-4.2.1.min.js | openssl base64 -A
For a local test file containing console.log("SRI example"); followed by a newline, that command was verified to produce:
aZEZqsD0hQTrQxOOiN+3hK9zrIM0coHjnIZM4RkuhmT3K0qz+sJn2b0NZNhsunyB
Prefix the result with sha384-. More importantly, hash the same artifact that visitors will receive. Hashing a source file and then asking a build tool, CDN, or optimizing proxy to minify or otherwise transform it produces different bytes and therefore a mismatch. A line-ending change is also a byte change.
A remote download can be hashed too, but it deserves caution: generating a digest from whatever a URL returns today does not establish that the file is trustworthy. Review the version and obtain it through a trusted release path first. SRI can preserve that decision; it cannot make the decision for you.
Add integrity metadata to scripts and styles
For a script hosted on another origin, the markup can look like this:
<script
src="https://cdn.example.com/library-4.2.1.min.js"
integrity="sha384-aZEZqsD0hQTrQxOOiN+3hK9zrIM0coHjnIZM4RkuhmT3K0qz+sJn2b0NZNhsunyB"
crossorigin="anonymous"></script>
The same mechanism protects an external stylesheet:
<link
rel="stylesheet"
href="https://cdn.example.com/theme-4.2.1.min.css"
integrity="sha384-REPLACE_WITH_THE_STYLESHEET_DIGEST"
crossorigin="anonymous">
The placeholder is deliberate: a stylesheet needs a digest calculated from its own bytes. Reusing the script digest would be incorrect.
Why crossorigin="anonymous" matters
For a cross-origin resource, SRI validation requires a CORS-eligible response. Adding crossorigin="anonymous" makes the element use CORS mode without sending cross-origin credentials. The asset server must also return an appropriate Access-Control-Allow-Origin header. If the CDN does not opt into CORS, adding a digest alone will not make the resource load successfully.
This is worth testing at the real page origin rather than only opening the asset URL in a tab. A direct request returning HTTP 200 does not prove that the browser's cross-origin request and integrity check will succeed.
A cautious rollout and update workflow
SRI works best when resource URLs identify immutable versions. A URL ending in 4.2.1 communicates a better contract than latest.js, but the name alone is not proof; the host must actually keep its bytes stable.
A small-site workflow can remain simple:
- Inventory externally loaded scripts and styles, including assets inserted by templates, plugins, consent tools, and tag managers.
- Remove dependencies that provide too little value for the trust they require.
- Choose a fixed version and review its release source and purpose.
- Obtain the exact artifact, calculate its digest, and add the matching
integrityvalue. - Confirm the asset host's CORS response and test the real page in a browser.
- Temporarily alter a test copy's digest and confirm that the browser blocks it and reports an integrity error in developer tools.
- Document how the version and digest are updated together, then monitor important pages after releases.
The deliberate bad-digest test belongs in a non-production copy. It checks the failure path without claiming that every browser, proxy, or deployment environment behaves identically beyond the standard's requirements.
What SRI does not solve
The name “integrity” can sound broader than the mechanism really is. Several boundaries matter:
- It does not make code safe. A vulnerable or privacy-invasive file can match its digest perfectly. SRI proves correspondence with expected bytes, not good behavior.
- It does not replace updates. Pinning an old library also pins its defects. Dependency monitoring and a controlled update process remain necessary.
- It does not replace HTTPS. On an HTTP page, an attacker who can modify the document can also remove or replace its integrity metadata.
- It does not guarantee availability. A vendor update, transformation, CORS error, or unavailable CDN can leave the protected resource blocked.
- It does not constrain every later request. A small loader whose bytes match can still fetch dynamic code or data. The trust analysis must include what the pinned script does after it starts.
- It is not a digital signature. The digest has no independent publisher identity. Anyone able to change both the HTML and the resource can replace both values.
Content Security Policy complements rather than duplicates SRI. A CSP can restrict which origins or script forms the page permits; SRI checks whether a particular fetched resource matches expected bytes. Self-hosting can reduce direct reliance on a third-party runtime host, but it transfers delivery, patching, and monitoring responsibilities to the site owner. None of these choices removes the need to understand the dependency.
When is SRI a good fit?
SRI is a strong fit for externally hosted, versioned JavaScript or CSS whose exact bytes should remain stable until a planned update. It is a poor fit for a URL intentionally serving changing content, unless the provider offers immutable versions or the site can redesign the integration. For a dynamic tag manager, pinning only the initial loader may create less protection than the markup suggests.
The practical question is therefore not “Should every external file get a hash?” It is “Can this dependency be reduced to a reviewed, immutable artifact with an update process we can operate?” If yes, SRI gives the browser a small but meaningful enforcement job. If no, the mismatch reveals a larger architectural decision: accept dynamic third-party control, isolate it more carefully, self-host an approved artifact, replace the dependency, or remove it.
Conclusion
Subresource Integrity turns an implicit promise into a browser-enforced comparison: this script or stylesheet must equal these expected bytes. Correct deployment requires the exact artifact, a suitable digest, CORS for cross-origin resources, browser testing, and a process that updates the URL and hash together.
Its limits are as important as its syntax. SRI does not certify authorship, discover vulnerabilities, preserve availability, or tame arbitrary dynamic loaders. Used where bytes are meant to be stable, however, it narrows a third-party trust decision into something the browser can actually check.
