Browser Enforcement & Security Boundaries
Permalink to "Browser Enforcement & Security Boundaries"This workflow is part of Core SRI Fundamentals & Browser Security Boundaries. Without active browser-side enforcement, the cryptographic hashes you compute at build time provide no real-world protection — a compromised CDN payload simply executes undetected. This page covers how to wire up the browser’s fetch-time integrity gate, align it with Content-Security-Policy, and keep both mechanisms in sync as assets rotate.
Prerequisites
Permalink to "Prerequisites"Conceptual Foundation
Permalink to "Conceptual Foundation"The W3C Subresource Integrity specification (published at https://www.w3.org/TR/SRI/) defines exactly one enforcement mechanism: a hash comparison performed by the browser after the network response body is fully received, and before the payload is handed to the JavaScript engine or CSS parser.
The browser extracts the algorithm prefix and base64-encoded digest from the integrity attribute, recomputes the digest over the raw response bytes, and either allows execution or fires a network-level abort. The abort happens before any script executes — the payload never touches the JavaScript heap. If multiple hash tokens are present (e.g. sha256-… sha384-…), the browser uses the strongest algorithm it supports and ignores weaker ones, which enables non-breaking algorithm migrations.
Critically, the crossorigin="anonymous" attribute is required on every cross-origin <script> or <link> element that carries an integrity attribute. Without it, the browser receives an opaque CORS response, which prevents hash computation, causing a guaranteed block even when the payload is unmodified. This is the single most common SRI misconfiguration in production.
Step 1 — Set Up the Execution Trust Model
Permalink to "Step 1 — Set Up the Execution Trust Model"Map every external asset to its origin and decide which enforcement mode applies. Strict mode (integrity attribute + matching CSP directive) blocks any payload whose hash diverges. Report-only mode (Content-Security-Policy-Report-Only) logs violations without blocking, which is useful during staged rollouts.
For each external dependency, record:
| Asset | CDN Origin | Hash Algorithm | Enforcement Mode |
|---|---|---|---|
react.production.min.js |
cdn.jsdelivr.net |
sha384 | enforce |
bootstrap.min.css |
cdn.jsdelivr.net |
sha384 | enforce |
analytics.js |
cdn.analytics-vendor.com |
sha384 | report-only |
Keep this manifest in version control. It becomes the source of truth for your CI/CD gate in step 3.
Step 2 — Generate Hashes Post-Minification
Permalink to "Step 2 — Generate Hashes Post-Minification"Hashes must be computed against the exact bytes that the CDN will serve — not the pre-minification source. Run hash generation as the final step of your build, after minification and any content transforms.
Command-line (OpenSSL):
# Generate a SHA-384 integrity hash for a local file
openssl dgst -sha384 -binary dist/react.production.min.js \
| openssl base64 -A \
| sed 's/^/sha384-/'
# Expected output: sha384-/bC7CKB3....
Node.js script (suitable for CI):
// scripts/generate-sri.mjs
import { createHash } from 'crypto';
import { readFileSync } from 'fs';
function sriHash(filePath, algorithm = 'sha384') {
const content = readFileSync(filePath);
const digest = createHash(algorithm).update(content).digest('base64');
return `${algorithm}-${digest}`;
}
const hash = sriHash('dist/react.production.min.js');
console.log(hash);
// sha384-/bC7CKBr...
Webpack via webpack-subresource-integrity:
// webpack.config.js
const { SubresourceIntegrityPlugin } = require('webpack-subresource-integrity');
module.exports = {
output: {
crossOriginLoading: 'anonymous',
},
plugins: [
new SubresourceIntegrityPlugin({
hashFuncNames: ['sha384'],
enabled: process.env.NODE_ENV === 'production',
}),
],
};
The plugin injects integrity and crossorigin attributes directly into the emitted HTML, eliminating any chance of template-hash drift.
Step 3 — Gate CI/CD on Hash Integrity
Permalink to "Step 3 — Gate CI/CD on Hash Integrity"A pre-deploy validation gate prevents a mismatched asset from ever reaching your CDN. The gate compares the manifest hashes recorded at build time against the hashes of the actual artifacts staged for deployment.
GitHub Actions example:
# .github/workflows/deploy.yml
jobs:
build-and-verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Build assets
run: npm run build
- name: Verify SRI manifest
run: |
node scripts/verify-sri-manifest.mjs \
--manifest dist/sri-manifest.json \
--algorithm sha384
# Exits with code 1 on any hash mismatch, blocking the deploy job
- name: Deploy
if: success()
run: npm run deploy
Verification script (scripts/verify-sri-manifest.mjs):
// Reads { "dist/react.production.min.js": "sha384-..." } from the manifest
// and recomputes each hash, failing fast on any divergence
import { createHash } from 'crypto';
import { readFileSync } from 'fs';
const manifest = JSON.parse(readFileSync('dist/sri-manifest.json', 'utf8'));
let allPassed = true;
for (const [file, expected] of Object.entries(manifest)) {
const [algo] = expected.split('-');
const actual = `${algo}-${createHash(algo).update(readFileSync(file)).digest('base64')}`;
if (actual !== expected) {
console.error(`MISMATCH: ${file}\n expected: ${expected}\n actual: ${actual}`);
allPassed = false;
}
}
if (!allPassed) process.exit(1);
console.log('All SRI hashes verified.');
The expected output on a clean deploy is:
All SRI hashes verified.
Step 4 — Align Content-Security-Policy Directives
Permalink to "Step 4 — Align Content-Security-Policy Directives"SRI attributes alone cannot prevent unauthorised inline script execution or resource substitution from origins not covered by an integrity attribute. Deploying a strict Content-Security-Policy alongside integrity attributes closes these gaps — see Configuring Content-Security-Policy with SRI for the full directive mapping and nonce configuration.
The minimal policy for a site that uses SRI on CDN scripts and stylesheets:
# Nginx: Combined CSP & SRI header
add_header Content-Security-Policy
"default-src 'self';
script-src 'self' 'strict-dynamic' https://cdn.jsdelivr.net;
style-src 'self' https://cdn.jsdelivr.net;
require-sri-for script style;
report-uri /csp-violation-report"
always;
The require-sri-for script style directive instructs the browser to block any <script> or <link rel="stylesheet"> element that lacks a valid integrity attribute, even if the source is listed in script-src. This turns SRI from an opt-in check on individual elements into a site-wide policy.
Cloudflare Workers equivalent:
// workers/headers.js
export default {
async fetch(request, env) {
const response = await env.ASSETS.fetch(request);
const headers = new Headers(response.headers);
headers.set(
'Content-Security-Policy',
[
"default-src 'self'",
"script-src 'self' 'strict-dynamic' https://cdn.jsdelivr.net",
"style-src 'self' https://cdn.jsdelivr.net",
"require-sri-for script style",
"report-uri /csp-violation-report",
].join('; ')
);
return new Response(response.body, { status: response.status, headers });
},
};
Step 5 — Design Cache Rotation and Hash Synchronisation
Permalink to "Step 5 — Design Cache Rotation and Hash Synchronisation"CDN caching introduces a time window in which stale assets can cause integrity failures. If you update an asset (and therefore its hash) but the CDN still serves the old payload from cache, every user who receives the new HTML but the old CDN response will encounter a hash mismatch block.
The correct deployment sequence is:
- Build the new asset and compute its new hash.
- Upload the asset to the CDN origin.
- Purge the CDN edge cache for that asset’s URL before updating any HTML.
- Update the HTML template (or the build output) with the new
integrityattribute. - Deploy the updated HTML.
Atomic deployments (e.g. Cloudflare Pages instant rollover, Vercel atomic commits) handle steps 3–5 in a single transaction. If your CDN does not support atomic purges, use content-addressed URLs (e.g. react.a3f8c2.min.js) to ensure the old URL continues to serve the old payload while the new URL is independently validated.
CDN cache purge via Cloudflare API:
curl -X POST \
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/purge_cache" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"files":["https://cdn.yourdomain.com/dist/react.production.min.js"]}'
Configuration Reference
Permalink to "Configuration Reference"| Option / Attribute | Valid Values | Security Implication |
|---|---|---|
integrity algorithm prefix |
sha256, sha384, sha512 |
SHA-384 is the recommended minimum; SHA-256 is still valid but offers lower collision resistance |
crossorigin attribute |
anonymous, use-credentials |
anonymous is required for cross-origin SRI; omission prevents hash computation and always blocks the resource |
script-src CDN origin |
full HTTPS origin (e.g. https://cdn.jsdelivr.net) |
Restricting to specific origins prevents fallback to untrusted CDNs |
require-sri-for |
script, style, script style |
Enforces integrity attributes site-wide; omitting it makes SRI per-element only |
Content-Security-Policy-Report-Only |
any valid CSP string | Safe for staged rollouts; violations are reported but not blocked |
| Multiple hash tokens | space-separated, e.g. sha256-… sha384-… |
Browser selects the strongest supported algorithm; enables zero-downtime algorithm migration |
Integration with Adjacent Tooling
Permalink to "Integration with Adjacent Tooling"The outputs of this workflow feed directly into the next layers of your security pipeline:
- SRI hashes → CSP
require-sri-forpolicy. Once every asset in a resource class (scripts, styles) carries a validated hash, enablingrequire-sri-forelevates the guarantee from per-element to site-wide. - Hash manifest → SBOM. The same asset list and version fingerprints you maintain for SRI are the raw material for a Generating CycloneDX SBOMs for Frontend Assets pipeline. The SBOM can then be submitted to vulnerability databases to detect known-compromised versions.
- CSP violation reports → telemetry. The
report-uriorreport-todirective sends browser-side violation events to your observability stack, giving you real-time signal when hash mismatches occur in the wild — useful for catching CDN propagation race conditions before users report them. - Fallback routing → Graceful Fallback Strategies. When enforcement triggers a block that cannot be resolved by a cache purge, a pre-verified secondary CDN origin can take over without compromising the security posture.
Troubleshooting
Permalink to "Troubleshooting"Failed to find a valid digest in the 'integrity' attribute for resource
The hash in the integrity attribute does not match the bytes the browser received. Most common causes: (1) the CDN is serving a cached, outdated file — purge the edge cache and redeploy; (2) the build pipeline computed the hash before minification but serves the minified file; (3) a gzip/Brotli decompression issue — SRI is computed over the decoded (decompressed) bytes, not the compressed response.
Subresource Integrity: The resource … was blocked because it violated a hash integrity check (opaque response)
The crossorigin attribute is missing or set incorrectly. Add crossorigin="anonymous" to the <script> or <link> element. Without it, the browser cannot read the response body to compute the hash.
Refused to load the script … because it violates the following Content Security Policy directive: "require-sri-for script"
A script element was rendered without an integrity attribute after require-sri-for script was enabled. Audit your HTML templates for dynamically injected <script> tags that bypass your build-time hash injection.
Hash mismatch only on some regions / CDN PoPs
Some CDN nodes are serving an old cached version. Use the CDN’s multi-region purge API (not a single-URL purge) or switch to content-addressed asset URLs to eliminate region-specific caching state.
SRI enforcement working locally but failing after deploy
This almost always means the production CDN serves a different file than the one hashed locally — check whether your build pipeline uses environment-specific transforms (e.g. API URL injection, feature flags) that alter the production bytes after the hash was computed.
require-sri-for breaks lazy-loaded dynamic imports
Dynamically imported modules (import('./chunk.js')) are not covered by require-sri-for in most browsers. Use per-element integrity attributes on dynamic <script> tags created programmatically, or evaluate Implementing Dynamic Script Loaders with Integrity for a pattern that attaches integrity hashes at runtime.
Security Boundary Note
Permalink to "Security Boundary Note"Browser enforcement via SRI does not protect against:
- Same-origin script injection. If an attacker can inject a
<script>tag pointing to your own origin, the browser will not apply SRI checks (same-origin responses are trusted by default unlessrequire-sri-forforces the check). - Post-execution DOM mutation. Once a script passes the integrity check and executes, it can modify the DOM, exfiltrate cookies, or register service workers. SRI is a fetch-time gate only — it provides no runtime sandbox.
- TLS-layer compromise. SRI validates payload bytes, not the identity of the server that delivered them. A certificate misdirection attack that keeps the payload unmodified passes SRI checks.
- Dependency-of-dependency attacks. If a verified script dynamically loads a second script without its own
integrityattribute, the second fetch is unprotected. This is the key motivation for the Dynamic Script Loading Patterns. - Build-time supply chain compromise. SRI guarantees the bytes you published are the bytes the browser runs — it cannot guarantee that those bytes were not already compromised in your build environment or package registry.
FAQ
Permalink to "FAQ"Does SRI enforcement also validate the TLS connection?
No. SRI validates the payload bytes after they are received, not the transport layer. A man-in-the-middle attack that terminates TLS and re-issues a certificate would still be blocked only if the attacker alters the payload. TLS, HSTS, and HPKP (where still relevant) are separate and complementary defences.
Why does the browser block the resource even when the CDN URL is in my CSP allowlist?
CSP allowlisting and SRI are independent checks. A URL can be permitted by a script-src directive yet still blocked if the integrity attribute is present and the computed hash diverges from the declared digest. Both checks must pass independently.
Can I use SHA-256 instead of SHA-384?
SHA-256 is valid and supported by all browsers, but the W3C SRI specification recommends SHA-384 or SHA-512 for new deployments. SHA-384 provides 192-bit collision resistance with a modest output overhead and is the current industry default for web assets.
What happens when the CDN serves a cached response with an outdated hash?
The browser blocks the resource because the computed hash of the stale payload no longer matches the new integrity attribute in the HTML. This is why atomic deployments that update HTML templates and purge edge caches simultaneously are essential — stale CDN responses are the most common source of production SRI failures.
Does removing the integrity attribute turn off enforcement?
Yes, for that element. Removing the attribute means no hash check is performed for that resource. If your CSP includes require-sri-for script, the browser will block any <script> without a valid integrity attribute regardless, so defence-in-depth requires both mechanisms.
Related
Permalink to "Related"- Configuring Content-Security-Policy with SRI — full directive mapping, nonce patterns, and
strict-dynamicconfiguration - Graceful Fallback Strategies — multi-origin failover when enforcement triggers an unexpected block
- Understanding Cryptographic Hash Algorithms — SHA-256 vs SHA-384 vs SHA-512 selection rationale and NIST compliance mapping
- Dynamic Script Loading Patterns — applying integrity checks to runtime-injected scripts and lazy-loaded modules