Core SRI Fundamentals & Browser Security Boundaries

Permalink to "Core SRI Fundamentals & Browser Security Boundaries"

Subresource Integrity (SRI) is a W3C browser security mechanism that binds a cryptographic digest to every external resource tag, preventing the browser from executing a script or applying a stylesheet unless its bytes match the declared hash exactly. It is the primary defence against compromised CDN delivery and third-party supply chain injection, and it is directly cited in PCI DSS v4.0.1, SOC 2 Type II, and ISO 27001 as a control for frontend asset verification.


The threat model: what SRI defends against

Permalink to "The threat model: what SRI defends against"

Modern frontend stacks load dozens of assets from third-party origins — JavaScript frameworks, analytics, fonts, icon sprites. Each of those fetches is a trust boundary: you control neither the CDN edge node nor the origin storage bucket, so a compromise anywhere in that chain can silently replace a legitimate file with a version that exfiltrates credentials or injects a skimmer.

Concrete attack vectors SRI interrupts:

  • CDN cache poisoning. An attacker replaces a cached .min.js with a patched version. Without SRI the browser executes it; with SRI, the digest mismatch blocks execution before a single instruction runs.
  • Registry typosquatting pulled at runtime. A compromised npm package published under a lookalike name serves a modified bundle from a CDN URL. SRI pins the exact bytes the developer audited.
  • Supply chain injection through a vendor’s own infrastructure. A third-party analytics provider’s S3 bucket is compromised and a skimmer is prepended to their tag. SRI catches the byte change immediately.
  • BGP hijack or anycast misdirection. Traffic to a CDN prefix is rerouted to a malicious edge that returns a manipulated file with a valid TLS certificate. SRI operates on response bytes, not on TLS origin, so the digest mismatch still fires.

SRI does not protect against:

  • Inline script injection (use a script-src CSP nonce or hash for that).
  • Same-origin asset tampering (same-origin fetches bypass the CORS/opacity constraint entirely).
  • Post-execution DOM mutation by code that already passed the integrity check.
  • Compromised TLS private keys (the attacker can serve an unmodified file with a valid certificate; SRI only detects byte-level changes).

Understanding this threat boundary is the foundation for pairing SRI correctly with Configuring Content-Security-Policy with SRI and with supply chain auditing controls.


The SRI fetch lifecycle

Permalink to "The SRI fetch lifecycle"

The diagram below shows exactly where in the browser’s resource-loading pipeline the integrity check fires and what each failure path produces.

SRI Fetch Lifecycle Diagram showing the browser's SRI verification pipeline: HTML parser encounters an integrity attribute, the browser issues a cross-origin fetch with CORS, the response body is hashed, the computed digest is compared to the declared digest, and the resource is either executed or blocked with a console error. HTML parser finds integrity="" Network fetch crossorigin=anonymous Hash computation SHA-384 over bytes Digest comparison computed vs declared Execute / apply PASS Block + console error MISMATCH Opaque no CORS request response body missing crossorigin match mismatch onerror handler fires → fallback load possible

The critical insight the diagram makes concrete: the crossorigin="anonymous" attribute is not optional boilerplate — without it the browser cannot read the response bytes and the integrity check is silently skipped, defeating the entire mechanism.


Core specification: attribute syntax and algorithm choices

Permalink to "Core specification: attribute syntax and algorithm choices"

The SRI specification (W3C Recommendation, 2016, maintained by the WebAppSec WG) defines the integrity attribute for <script> and <link rel="stylesheet"> elements. The value is one or more whitespace-separated hash tokens, each in the form <algorithm>-<base64-digest>.

<!-- Canonical SRI syntax — SHA-384 is the recommended default algorithm -->
<script
  src="https://cdn.example.com/framework.min.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"
></script>

<!-- Stylesheet variant -->
<link
  rel="stylesheet"
  href="https://cdn.example.com/styles.min.css"
  integrity="sha384-3cskD8shRqChMKCMlMNPezEwv0GFHsT7OxGnbFPeK4mvPkVGKpkB7Vp0jLvIFRE"
  crossorigin="anonymous"
/>

Algorithm comparison

Permalink to "Algorithm comparison"

The three supported algorithms differ in collision resistance and output size. Understanding Cryptographic Hash Algorithms covers the mathematical tradeoffs in depth; the table below gives the operational view:

Algorithm Output bits Collision resistance Key standard Recommended use
SHA-256 256 128-bit FIPS 180-4 Acceptable; minimum viable for new work
SHA-384 384 192-bit FIPS 180-4 Default for production SRI
SHA-512 512 256-bit FIPS 180-4 High-assurance or PCI-scoped pages

NIST SP 800-131A Rev 2 deprecated SHA-1 for digital signatures and hash-based verification in 2019. MD5 has been broken for collision attacks since 2004. Neither must appear in SRI integrity values. Modern browsers reject both.

Multiple-hash syntax for algorithm migration

Permalink to "Multiple-hash syntax for algorithm migration"

You can list two tokens on a single integrity attribute. The browser selects the strongest algorithm it supports:

<script
  src="https://cdn.example.com/lib.min.js"
  integrity="sha256-abc123... sha384-xyz456..."
  crossorigin="anonymous"
></script>

Use this during a transition from SHA-256 to SHA-384 so older browsers (where SHA-384 support was added in 2015 and is now universal) continue to verify while you confirm all edge caches carry the new hash.


Browser enforcement mechanics

Permalink to "Browser enforcement mechanics"

The browser’s integrity check is a fetch-time gate, not a runtime sandbox. Understanding its exact execution point and failure modes is essential for designing reliable fallback strategies.

Enforcement lifecycle (detailed)

Permalink to "Enforcement lifecycle (detailed)"
  1. The HTML parser encounters an element with an integrity attribute.
  2. The browser issues a CORS-eligible request. The crossorigin="anonymous" attribute is mandatory; its absence causes the browser to issue a no-CORS opaque fetch whose bytes cannot be read, silently bypassing the check.
  3. The CDN must respond with Access-Control-Allow-Origin: * (or the origin). Without this, the CORS preflight or response read fails before hashing can begin.
  4. The browser reads the full response body into memory and computes the digest using the specified algorithm.
  5. The computed base64 digest is compared character-for-character against the integrity token(s). Comparison is case-sensitive.
  6. Match: execution or stylesheet application proceeds normally.
  7. Mismatch: the resource is blocked. A SecurityError is thrown internally, a net::ERR_SRI_FAILED message appears in the DevTools console, and if an onerror event handler is attached to the element it fires. Crucially, no load event fires.

For a full breakdown of enforcement configuration — including how CSP’s require-sri-for directive extends this gate to every resource in a given class — see Browser Enforcement & Security Boundaries.

Failure modes that are not SRI mismatches

Permalink to "Failure modes that are not SRI mismatches"
Failure Symptom Not an SRI issue
CORS preflight rejected net::ERR_FAILED with CORS error CDN missing Access-Control-Allow-Origin
TLS certificate error net::ERR_CERT_* TLS layer, not integrity layer
404 / CDN purge net::ERR_ABORTED Resource missing entirely
Non-deterministic build Hash mismatch every deploy Build tooling, not browser bug

Production implementation patterns

Permalink to "Production implementation patterns"

Webpack

Permalink to "Webpack"

The webpack-subresource-integrity plugin computes digests after minification and injects them into the emitted HTML automatically:

// webpack.config.js
const { SubresourceIntegrityPlugin } = require('webpack-subresource-integrity');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  output: {
    crossOriginLoading: 'anonymous',
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' }),
    new SubresourceIntegrityPlugin({ hashFuncNames: ['sha384'] }),
  ],
};

The crossOriginLoading: 'anonymous' output option is mandatory — without it, dynamically injected chunks from code-splitting will not carry the crossorigin attribute. For a full Webpack walkthrough with chunk hashing and source-map considerations, see Automating Hash Generation in Webpack 5.

Vite

Permalink to "Vite"
// vite.config.ts
import { defineConfig } from 'vite';
import sri from 'vite-plugin-subresource-integrity';

export default defineConfig({
  plugins: [sri({ algorithms: ['sha384'] })],
});

Vite’s content-hashed filenames (app.Bx7kqL9a.js) already guarantee immutability; the SRI plugin adds the digest-to-HTML injection step.

Nginx server-side injection for third-party scripts

Permalink to "Nginx server-side injection for third-party scripts"

When you do not own the build pipeline for a third-party snippet, generate the hash at deploy time and inject it via a server-side include:

# nginx.conf — add_header for the SRI-verified asset via SSI
ssi on;

# In your HTML template:
# <!--# include file="/sri/framework-hash.txt" -->
# Then set Content-Security-Policy accordingly.

A more composable pattern uses Cloudflare Workers to rewrite <script> tags in flight, appending the current hash from a KV-stored manifest before the browser receives the response.

CI/CD hash gating

Permalink to "CI/CD hash gating"
#!/usr/bin/env bash
# post-build-sri-check.sh — run after npm run build
set -euo pipefail

DIST_DIR="dist"
HTML_FILE="${DIST_DIR}/index.html"

# Extract every integrity value from the built HTML and re-compute each
grep -oP 'integrity="sha384-\K[^"]+' "$HTML_FILE" | while read -r declared; do
  # find the corresponding src
  src=$(grep -oP "src=\"[^\"]+(?= integrity)" "$HTML_FILE" | head -1 | cut -d'"' -f2)
  computed=$(openssl dgst -sha384 -binary "${DIST_DIR}${src}" | openssl base64 -A)
  if [ "$declared" != "$computed" ]; then
    echo "FAIL: hash mismatch for ${src}" >&2
    exit 1
  fi
done

echo "All SRI hashes verified."

Cross-cutting architecture considerations

Permalink to "Cross-cutting architecture considerations"

CORS requirements

Permalink to "CORS requirements"

SRI is only effective when combined with CORS. Every CDN serving SRI-tagged assets must include Access-Control-Allow-Origin in its response. The two most common misconfigurations:

  1. Missing CORS header on the CDN. The browser either cannot read the response (CORS error) or issues an opaque no-CORS fetch that bypasses the check. Either way, SRI provides no protection.
  2. Caching a CORS response without Vary: Origin. A CDN that caches a CORS response and later serves it to a non-CORS request (or vice versa) can produce stale responses that fail the check for some users.

CSP interaction

Permalink to "CSP interaction"

Content Security Policy and SRI are complementary, not overlapping:

Mechanism Controls Cannot control
SRI Byte-level integrity of a specific fetched resource What scripts are allowed to run (allowlisting)
CSP script-src Which origins and inline scripts are allowed Whether a permitted script was tampered with
CSP require-sri-for Enforces that every script/style in the page carries an integrity attribute Correctness of the hash value itself

A hardened configuration uses all three together. Configuring Content-Security-Policy with SRI details the header syntax and priority rules.

COOP / COEP and isolation headers

Permalink to "COOP / COEP and isolation headers"

Cross-Origin Opener Policy (COOP) and Cross-Origin Embedder Policy (COEP) affect the browser’s process isolation model. Resources loaded in a COEP-enabled context must themselves be CORS-eligible (they need crossorigin and a permissive CORS response) — exactly the same prerequisite that SRI demands. Enabling both together is therefore architecturally consistent and does not require separate CDN configuration.

Dynamic script injection

Permalink to "Dynamic script injection"

Scripts injected via document.createElement('script') support SRI, but integrity and crossorigin must be set before the element is appended to the DOM:

// Correct: attributes set before DOM insertion
const s = document.createElement('script');
s.src = 'https://cdn.example.com/widget.min.js';
s.integrity = 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC';
s.crossOrigin = 'anonymous';
document.head.appendChild(s);

Setting integrity after appendChild has no effect; the browser has already initiated the fetch. For patterns around loader functions and runtime hash resolution, see Implementing Dynamic Script Loaders with Integrity.


Compliance mapping

Permalink to "Compliance mapping"

PCI DSS v4.0.1 — Requirement 6.4.3

Permalink to "PCI DSS v4.0.1 — Requirement 6.4.3"

Requirement 6.4.3 became mandatory on 31 March 2025. It applies to all scripts loaded by and executed in the consumer’s browser on payment pages:

"All payment page scripts that are loaded and executed in the consumer’s browser are managed as follows:

  • A method is implemented to confirm that each script is authorized.
  • A method is implemented to assure the integrity of each script.
  • An inventory of all scripts is maintained with written justification as to why each is necessary."

SRI satisfies the integrity assurance element. The “authorized” element requires a documented allowlist (typically driven by CSP script-src combined with the hash inventory from your CI pipeline). The inventory element is satisfied by the build-time SRI manifest your tooling generates.

SOC 2 Type II — Change Management

Permalink to "SOC 2 Type II — Change Management"

SOC 2 CC8.1 requires that changes to system components (including third-party scripts) are authorized, tested, and documented. An automated SRI pipeline that regenerates hashes on every build and gates deployment on hash validity directly satisfies the “tested and documented” sub-criteria.

ISO 27001:2022 — A.8.9 Configuration Management and A.8.20 Network Security

Permalink to "ISO 27001:2022 — A.8.9 Configuration Management and A.8.20 Network Security"

A.8.9 requires that configurations of assets (including software components) be established, documented, and managed throughout their lifecycle. A.8.20 requires that networks be managed and controlled to protect information in systems and applications. SRI-enforced asset pinning maps to both controls.


Operational runbook

Permalink to "Operational runbook"

Hash rotation SLA

Permalink to "Hash rotation SLA"

Define rotation triggers in your security runbook:

Trigger SLA Action
Vendor releases updated bundle 24 h Regenerate hash; deploy updated HTML
CDN vendor security advisory 4 h Pin to a known-good bundle; notify team
CI hash mismatch in staging Block deploy Investigate before promoting to production
Scheduled dependency audit Weekly Re-run hash generation; compare to committed manifest

Mismatch telemetry

Permalink to "Mismatch telemetry"

Browser-native SecurityPolicyViolationEvent fires when a CSP require-sri-for directive is active, but SRI mismatches without CSP do not emit a structured event — they only produce a console error. Capture them by attaching an onerror listener:

// Centralized SRI mismatch reporter — attach before any SRI-tagged script loads
document.addEventListener('error', (e) => {
  const target = e.target;
  if (target instanceof HTMLScriptElement && target.integrity) {
    fetch('/api/sri-violation', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        src: target.src,
        integrity: target.integrity,
        timestamp: Date.now(),
        userAgent: navigator.userAgent,
      }),
      keepalive: true,
    });
  }
}, true /* capture phase */);

Rollback procedure

Permalink to "Rollback procedure"
  1. Identify the last known-good asset version from your CDN or build artifact store.
  2. Re-compute its SHA-384 digest: openssl dgst -sha384 -binary asset.js | openssl base64 -A.
  3. Update the integrity attribute in your HTML or HTML template.
  4. Deploy the updated HTML. The rollback is complete in one deploy cycle.
  5. Log the incident in your change management system with the affected asset URL, hash before and after, and time-to-detect.

Common pitfalls and mitigations

Permalink to "Common pitfalls and mitigations"
Issue Root cause Fix
crossorigin attribute omitted Treated as optional; CDN request is no-CORS Always include crossorigin="anonymous" on every SRI-tagged element
Hash computed from development source Minification and transpilation change bytes Always hash the final production artifact, not the source file
Non-deterministic build output Timestamps, content-hashes, or plugin ordering differs between runs Pin all build tooling versions; use deterministic build flags (e.g. Webpack output.hashSalt)
SHA-1 or MD5 used Legacy tooling default Browsers reject both; enforce SHA-384 in your pipeline config
Static hash hardcoded without CI regeneration Manual one-time setup Automate hash injection as part of every build step
CORS header missing on CDN CDN origin rule not configured Add Access-Control-Allow-Origin: * to CDN response rules; include Vary: Origin
require-sri-for CSP directive missing SRI is advisory, not enforced at policy level Add Content-Security-Policy: require-sri-for script style in staging first, then production
Ignoring onerror events No monitoring for mismatch failures Implement the capture-phase error listener and forward to your observability pipeline

FAQ

Permalink to "FAQ"
Does SRI protect against XSS attacks?

No. SRI verifies the integrity of externally fetched resources at fetch time. It does not prevent inline script injection, DOM clobbering, or same-origin attacks. Use a strict Content-Security-Policy with a nonce or hash-based script-src to address inline injection, and Trusted Types to restrict DOM sinks.

Why is crossorigin="anonymous" required for SRI to work?

Without crossorigin="anonymous", the browser issues a no-CORS fetch that produces an opaque response. The browser cannot read the bytes of an opaque response, so hash computation is impossible and the integrity check is silently skipped — the resource loads as if no integrity attribute was present. This is the single most common real-world SRI bug.

Can I use SHA-256 instead of SHA-384?

SHA-256 provides 128-bit collision resistance and is supported by all major browsers. It is acceptable for general web assets. SHA-384 is the W3C recommendation for SRI and provides 192-bit resistance; it is the safer default for production systems and the required minimum for high-risk pages such as payment flows. The Understanding Cryptographic Hash Algorithms page gives the full comparison including how to calculate SHA-256 vs SHA-384 for SRI.

What happens when an SRI check fails in production?

The browser blocks script execution or stylesheet application and logs net::ERR_SRI_FAILED in the DevTools console. Any onerror handler attached to the element fires; the load event does not. Without a fallback, the blocked resource is silently unavailable and any code that depends on it will throw runtime errors. For patterns that recover gracefully, see Graceful Fallback Strategies.

Does SRI work with dynamically injected scripts?

Yes, but integrity and crossorigin must be set on the HTMLScriptElement before it is appended to the DOM. Setting them after appendChild has no effect because the browser initiates the fetch at insertion time. The Implementing Dynamic Script Loaders with Integrity page shows production-safe loader patterns.

Is SRI required for PCI DSS compliance?

PCI DSS v4.0.1 Requirement 6.4.3 (mandatory since 31 March 2025) requires that all scripts loaded on payment pages are inventoried, authorized, and integrity-verified. SRI is the primary browser-native mechanism satisfying the integrity-verification component of that requirement. Combine it with a CSP script-src allowlist and a build-time hash inventory for full compliance coverage.

Can SRI be used with module scripts and import maps?

Yes. The integrity attribute applies to <script type="module"> the same way it does to classic scripts. Import maps (<script type="importmap">) can include an integrity field per module URL, enabling SRI verification for ES module specifiers. Browser support for integrity in import maps is available in Chromium 104+ and Safari 17.2+; Firefox support is available from v115.


Permalink to "Related"

In This Section