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.jswith 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-srcCSP 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.
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)"- The HTML parser encounters an element with an
integrityattribute. - 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. - 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. - The browser reads the full response body into memory and computes the digest using the specified algorithm.
- The computed base64 digest is compared character-for-character against the
integritytoken(s). Comparison is case-sensitive. - Match: execution or stylesheet application proceeds normally.
- Mismatch: the resource is blocked. A
SecurityErroris thrown internally, anet::ERR_SRI_FAILEDmessage appears in the DevTools console, and if anonerrorevent handler is attached to the element it fires. Crucially, noloadevent 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:
- 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.
- 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"- Identify the last known-good asset version from your CDN or build artifact store.
- Re-compute its SHA-384 digest:
openssl dgst -sha384 -binary asset.js | openssl base64 -A. - Update the
integrityattribute in your HTML or HTML template. - Deploy the updated HTML. The rollback is complete in one deploy cycle.
- 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.
Related
Permalink to "Related"- Understanding Cryptographic Hash Algorithms — SHA-256 vs SHA-384 vs SHA-512 tradeoffs, digest generation commands, and algorithm migration guidance.
- Browser Enforcement & Security Boundaries — Detailed enforcement lifecycle, CSP
require-sri-for, and COOP/COEP interaction. - Graceful Fallback Strategies —
onerrorhandler patterns, secondary origin routing, and CSP report-only monitoring. - Static Asset Hash Generation — Build-tool integration (Webpack, Vite, Rollup) and CI/CD pipeline gating for automated digest injection.
- Supply Chain Auditing & Dependency Verification — Lockfile integrity, SBOM generation, and provenance verification as the broader context for SRI in a defence-in-depth strategy.