Home SECURITY

Subresource Integrity (SRI)

SRI protects pages from compromised CDNs by pinning the cryptographic hash of an external script or stylesheet. The browser refuses to execute a resource whose hash does not match the integrity attribute.

What SRI protects against

A page that loads a script from a CDN trusts that CDN. If the CDN is compromised — or if a DNS poisoning, a TLS downgrade, or a malicious BGP route hijack succeeds — the attacker can serve a modified script and the page will execute it.

Subresource Integrity adds an integrity attribute carrying the hash of the expected file. The browser computes the hash of what it received; if it differs, the resource is rejected and the script does not run. The crossorigin="anonymous" attribute is required for cross-origin loads so the response is read into a form the integrity check can hash.

<script src="https://cdn.example/lib-1.4.2.js"
        integrity="sha384-Bg5IxjkYECdpEd0+Fz9kZD7+8n4gPRKt9YLVbXl5W+IOH7BtBgIQbyV3l/G6JUxz"
        crossorigin="anonymous"></script>

<link rel="stylesheet"
      href="https://cdn.example/lib-1.4.2.css"
      integrity="sha384-z23gFw7w53JF1YIuJ06JQv4eFErXaYY9jBuGeoGiYZbE9p1qrjrUUaWDtxvUI3mt"
      crossorigin="anonymous">

What integrity accepts

The attribute is a space-separated list of <algo>-<base64-hash> tokens. The user agent picks the strongest algorithm it supports and uses the corresponding hash.

Supported algorithms (per the SRI spec):

  • sha256 — fastest, large attack surface against pre-image.
  • sha384 — recommended default.
  • sha512 — strongest in current SRI deployment; about 30% of observed integrity attributes use it (HTTP Archive 2024).

Including more than one algorithm is allowed and forward- compatible: integrity="sha256-... sha384-...". The browser picks the strongest it knows.

Generating the hash

openssl dgst -sha384 -binary lib-1.4.2.js | openssl base64 -A

Most build pipelines automate this. Vite, Webpack, Rollup, and esbuild all have plugins (vite-plugin-sri, webpack-subresource-integrity) that compute and inject the hash for every emitted asset.

About 8% of the top 10,000 sites on the Tranco list 2024 use SRI on at least one external resource — a small share but growing about 1.5 percentage points per year.

What SRI does not protect

  • Same-origin resources are not the threat model. SRI is designed for third-party and CDN-hosted assets where the origin is outside the page’s trust boundary.
  • Side-channel exfiltration — a script that runs successfully can still send data to an attacker. Combine SRI with Content Security Policy to constrain what a verified script can do.
  • HTML and JSON resources. SRI applies only to <script> and <link rel="stylesheet"> (and <link rel="preload"> of those types). HTML inclusion via fetch() is not SRI-checked by the browser; the application layer must verify.
  • Dynamic imports. ES module dynamic import() statements ignore the integrity attribute on the static <script>. Use the import maps integrity field for dynamic imports — the proposal reached cross-engine development in 2024.

What happens on mismatch

If the hash does not match, the user agent:

  1. Refuses to execute the script (or apply the stylesheet).
  2. Logs a console error.
  3. Fires the error event on the element so JS can detect.
  4. Treats the load as failed for purposes of network metrics.

The error message is engine-specific but always includes the expected and computed hashes. About 0.4% of SRI-protected loads fail at any given time per the HTTP Archive 2024 security report — mostly due to cache poisoning at intermediate proxies and content-encoding mismatches, not actual attacks.

Cross-engine support

SRI reached cross-engine support in 2018 across Chromium, WebKit, and Gecko. The WPT subset for SRI passes at about 99% in all three engines as of 2024.

caniuse for SRI reports about 96% global support.

Common pitfalls

  • crossorigin missing on cross-origin resource. The integrity check does not run; the load proceeds without verification. The behaviour is silent unless you check the network panel.
  • Hash from a different version. Updating the upstream library changes the hash. Pin the version in the URL (lib-1.4.2.js, not lib-latest.js) so the integrity match is stable.
  • CDN that adds compression headers. Some CDNs serve a re-compressed payload; the hash computed by your build no longer matches. Use a CDN that returns the same bytes you uploaded (or use SRI only on assets you control).
  • Embedded integrity in HTML at build time, dynamic asset resolution at runtime. A revved filename mismatch surfaces as an empty page; the script load fails silently. CI smoke tests catch this.
  • HTTP/2 server push. The pushed asset is checked against the integrity at the time of consumption, not push, so push preloads may sit idle if hashes change between push and consumption.

Further reading