CDN Trust Mapping & Routing
Permalink to "CDN Trust Mapping & Routing"This workflow is part of Asset Hashing & Dynamic Script Injection. Without explicit trust boundaries between your CDN origins and the assets they serve, a compromised edge node, a misconfigured origin, or a supply chain substitution can silently replace a JavaScript bundle with malicious bytes — and no CSP header alone will catch it before the browser executes the payload.
The workflow on this page binds cryptographic hashes to routing rules so every CDN origin is either explicitly trusted (and carries a verified hash manifest) or blocked before the response reaches the user. It covers multi-CDN tiering, CI/CD manifest gating, configuration reference, troubleshooting, and monitoring.
Prerequisites
Permalink to "Prerequisites"Conceptual Foundation: How Origin Trust Tiers Work
Permalink to "Conceptual Foundation: How Origin Trust Tiers Work"The W3C SRI specification (§3.3) delegates all integrity enforcement to the browser: the browser fetches the resource, decodes any Content-Encoding compression, computes the digest of the decoded bytes, and compares it against the integrity attribute. The CDN’s sole obligation is to deliver bytes unmodified.
That gives rise to a clean separation of responsibilities:
- Browser — verifies that the bytes it received match the hash in the DOM.
- CDN — routes traffic and caches responses without mutating the decoded payload.
- Origin CI pipeline — generates hashes and publishes the authoritative manifest.
- Routing rules — enforce which origins are permitted to serve a given path.
Multi-CDN architectures expand the attack surface because a secondary CDN that is not in the routing allow-list can still be DNS-reachable. Classifying origins into trust tiers and coupling those tiers to routing policy closes that gap.
| Tier | Origin type | Verification strictness |
|---|---|---|
| Internal | Your own cloud storage (S3, GCS, R2) | Hash manifest required; CDN transformation disabled |
| Partner | Known third-party CDN under contract (Fastly, Akamai) | Hash manifest required; Content-Security-Policy: require-sri-for script style enforced |
| Public | Open registries (unpkg, jsDelivr, cdnjs) | Hash manifest required; integrity attribute mandatory; no fallback without hash |
The diagram below illustrates how a build pipeline’s hash manifest flows through origin tiers to browser validation.
Step 1 — Classify Your CDN Origins and Assign Trust Tiers
Permalink to "Step 1 — Classify Your CDN Origins and Assign Trust Tiers"Before writing a line of routing configuration, list every origin that serves assets referenced by your HTML. For each origin, record: the domain, whether your team controls it, whether the CDN provider has a signed SLA, and whether you can disable CDN-side payload transformation.
# Audit script: extract all unique script/link src domains from a built HTML directory
grep -rEoh 'src="https?://([^/"]+)' dist/ | \
sed 's/src="https\?:\/\///' | sort -u > cdn-origins.txt
cat cdn-origins.txt
Expected output — a deduplicated list of hostnames, e.g.:
assets.example.com
cdn.jsdelivr.net
static.cloudflareinsights.com
unpkg.com
Assign each hostname to a tier (internal / partner / public) and record the result in cdn-trust-manifest.json alongside your build artifacts. This file becomes the authoritative source for the routing rule generator in Step 3.
Step 2 — Generate a Verified Hash Manifest at Build Time
Permalink to "Step 2 — Generate a Verified Hash Manifest at Build Time"The hash manifest must be produced before any file reaches a CDN. Use Static Asset Hash Generation workflows to automate this. The manifest records the SHA-384 digest for every artifact and maps it to the CDN path it will occupy.
// scripts/generate-sri-manifest.js
// Run: node scripts/generate-sri-manifest.js --out dist/sri-manifest.json
import { createHash } from 'node:crypto';
import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';
const DIST = new URL('../dist', import.meta.url).pathname;
const OUT = process.argv[process.argv.indexOf('--out') + 1];
function hashFile(filePath) {
const bytes = readFileSync(filePath);
return 'sha384-' + createHash('sha384').update(bytes).digest('base64');
}
function walk(dir) {
return readdirSync(dir).flatMap(name => {
const full = join(dir, name);
return statSync(full).isDirectory() ? walk(full) : [full];
});
}
const manifest = {};
for (const file of walk(DIST)) {
if (/\.(js|css|woff2|svg)$/.test(file)) {
const key = '/' + relative(DIST, file);
manifest[key] = hashFile(file);
}
}
writeFileSync(OUT, JSON.stringify(manifest, null, 2));
console.log(`Manifest written: ${Object.keys(manifest).length} assets → ${OUT}`);
Verification signal — the script exits with code 0 and prints a count. Wire it into your CI stage:
# .github/workflows/deploy.yml (excerpt)
- name: Generate SRI manifest
run: node scripts/generate-sri-manifest.js --out dist/sri-manifest.json
- name: Fail if manifest is empty
run: |
COUNT=$(jq 'length' dist/sri-manifest.json)
if [ "$COUNT" -lt 1 ]; then echo "Empty manifest — aborting"; exit 1; fi
echo "Manifest contains $COUNT assets"
Step 3 — Publish the Manifest to CDN Routing Configuration
Permalink to "Step 3 — Publish the Manifest to CDN Routing Configuration"Push the manifest hashes into your CDN’s edge configuration. The exact API differs by provider; the example below targets the Cloudflare Workers KV store that a Worker reads at request time to enforce origin routing.
// scripts/sync-cdn-routing.js
// Run: node scripts/sync-cdn-routing.js --manifest dist/sri-manifest.json
import { readFileSync } from 'node:fs';
const manifest = JSON.parse(
readFileSync(process.argv[process.argv.indexOf('--manifest') + 1], 'utf8')
);
const CF_ACCOUNT = process.env.CF_ACCOUNT_ID;
const CF_NS = process.env.CF_KV_NAMESPACE_ID;
const CF_TOKEN = process.env.CF_API_TOKEN;
const body = JSON.stringify(
Object.entries(manifest).map(([key, hash]) => ({
key,
value: JSON.stringify({ hash, tier: 'internal', allowedAt: new Date().toISOString() }),
}))
);
const res = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT}/storage/kv/namespaces/${CF_NS}/bulk`,
{
method: 'PUT',
headers: {
'Authorization': `Bearer ${CF_TOKEN}`,
'Content-Type': 'application/json',
},
body,
}
);
if (!res.ok) {
const err = await res.text();
console.error('CDN routing sync failed:', err);
process.exit(1);
}
console.log(`Synced ${manifest.length ?? Object.keys(manifest).length} routing entries to KV`);
For Fastly, replace the fetch call with a Fastly NGWAF ACL update or a VCL snippet deploy via the Fastly Config API. For Akamai, use the Property Manager API to update the match condition on the origin routing rule.
Step 4 — Set integrity Attributes and CSP Headers on Origin Responses
Permalink to "Step 4 — Set integrity Attributes and CSP Headers on Origin Responses" With the manifest in place, stamp every HTML <script> and <link> tag with the matching hash. If you use Automating Hash Generation in Webpack 5, the webpack-subresource-integrity plugin handles this automatically. For server-side rendering or static site generators, read the manifest at build time:
<!-- Correct: integrity + crossorigin both present -->
<script
src="https://assets.example.com/app.fca3b9.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux8JHrklT1RNnZ3d8l6+pNnS3lPmW2"
crossorigin="anonymous"
></script>
The crossorigin="anonymous" attribute is not optional. Without it, the browser sends credentials in the request and the CORS preflight is skipped — but SRI requires a CORS-eligible response for cross-origin resources. Omitting crossorigin causes a silent fetch-then-block failure that is extremely difficult to diagnose.
Pair the tag with a Configuring Content-Security-Policy with SRI header:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://assets.example.com 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux8JHrklT1RNnZ3d8l6+pNnS3lPmW2';
require-sri-for script;
report-to sri-violations
require-sri-for script instructs compliant browsers to refuse any <script> that lacks an integrity attribute, providing defence-in-depth against dynamically injected tags that the manifest does not cover. For runtime-injected scripts, consult Dynamic Script Loading Patterns.
Step 5 — Gate the CI/CD Pipeline on Mismatch Rate
Permalink to "Step 5 — Gate the CI/CD Pipeline on Mismatch Rate"A deployment gate that reads live CSP violation reports prevents a broken manifest from propagating to production. The gate queries your reporting endpoint for the candidate bundle, computes the mismatch rate over the last N minutes, and blocks promotion if the rate exceeds a threshold.
#!/usr/bin/env bash
# scripts/check-sri-violation-rate.sh
# Usage: ./check-sri-violation-rate.sh <bundle-version> <threshold-percent>
set -euo pipefail
VERSION="${1:?Usage: check-sri-violation-rate.sh <version> <threshold>}"
THRESHOLD="${2:-1}" # default: block if >1% of loads fail
VIOLATIONS=$(curl -sf "${REPORT_ENDPOINT}/violations?version=${VERSION}&minutes=10" | jq '.count')
LOADS=$(curl -sf "${REPORT_ENDPOINT}/loads?version=${VERSION}&minutes=10" | jq '.count')
if [ "$LOADS" -eq 0 ]; then
echo "No load data yet — skipping gate (first deploy or pre-prod)"
exit 0
fi
RATE=$(echo "scale=2; $VIOLATIONS * 100 / $LOADS" | bc)
echo "SRI mismatch rate: ${RATE}% (threshold: ${THRESHOLD}%)"
if (( $(echo "$RATE > $THRESHOLD" | bc -l) )); then
echo "FAIL: mismatch rate exceeds threshold — blocking promotion"
exit 1
fi
echo "PASS: mismatch rate within threshold"
Integrate this gate into the promote stage, not the build stage — run it after canary traffic has been routed to the new bundle for 10–15 minutes.
Step 6 — Monitor, Alert, and Roll Back on Anomalies
Permalink to "Step 6 — Monitor, Alert, and Roll Back on Anomalies"Real-time telemetry from report-to violation endpoints is the primary signal for origin compromise. Configure your observability stack to alert when:
- The per-asset mismatch rate exceeds 0.5% over a 5-minute rolling window
- A new
violated-directive: script-srcevent references a path that is not in the current manifest - Violation events correlate with a third-party CDN origin update (check CDN changelog webhooks)
Rollback procedure when mismatch rates spike:
- Identify the affected asset path from the violation report’s
blocked-urifield. - Check the CDN KV store — confirm the stored hash matches the build manifest. Divergence indicates CDN-side mutation or a race condition from a concurrent deployment.
- Purge the CDN cache for the affected path and re-serve from the internal origin.
- Re-deploy the previous verified bundle if purge does not resolve the mismatch.
- Post-incident: audit CDN transformation rules for the affected path; verify CORS headers are present and the
Content-Encodingwas not altered mid-flight.
For browser-enforced failure handling, see Handling SRI Failures with onerror Handlers.
Configuration Reference
Permalink to "Configuration Reference"| Option | Valid values | Security implication |
|---|---|---|
integrity algorithm |
sha256-, sha384-, sha512- |
SHA-384 is the industry default; SHA-512 adds marginal security at higher payload cost; SHA-256 is insufficient for new deployments |
crossorigin |
anonymous, use-credentials |
Must be anonymous for public CDN assets; use-credentials leaks session cookies in the SRI fetch |
require-sri-for |
script, style, script style |
Apply to both to prevent stylesheet injection; script alone is the minimum for PCI DSS 6.4.3 compliance |
| CDN transformation | disabled / edge-side includes off | Any payload-mutating transformation invalidates the hash; disable for all SRI-protected paths |
| Cache purge coupling | Tied to manifest publish step | Purge must run after the new manifest is live — purging before causes a window where browsers receive new bytes with old hashes |
| Fallback origin | Same content-hashed path | Fallback origins must serve byte-identical content; never route to an origin that re-minifies or re-bundles at serve time |
Integration with Adjacent Tooling
Permalink to "Integration with Adjacent Tooling"CDN trust mapping sits between the hash generation step and the browser’s enforcement layer. Its output feeds forward into two adjacent workflows:
- Mapping CDN Origins to SRI Policies — the detailed reference page that documents how to author the per-origin policy matrix, including how to map a CDN edge PoP list to a
Content-Security-Policyscript-srcallow-list. - Browser Enforcement & Security Boundaries — once the CDN delivers bytes, this page documents exactly what the browser does: the fetch lifecycle, the hash computation algorithm, and every failure mode including opaque-response blocking.
For SBOM traceability — recording which CDN origin version served which bundle hash and when — combine this workflow with Automated SBOM Generation, which produces CycloneDX artifacts that reference SRI hashes as component evidence.
Troubleshooting
Permalink to "Troubleshooting"net::ERR_SRI_FAILED — resource blocked in DevTools
The hash in the integrity attribute does not match the decoded bytes received. Most common cause: CDN-side minification or HTML rewriting is enabled for the path. Disable all payload-transforming rules for the affected path and hard-reload. Confirm by downloading the raw URL with curl --compressed and computing openssl dgst -sha384 -binary <file> | openssl base64 -A.
Refused to load the script ... it requires a valid nonce or hash to be present in the source list
The CSP require-sri-for script directive is active but the <script> tag lacks an integrity attribute. This occurs when a dynamic script injection path (analytics, A/B testing, tag managers) creates a tag without reading from the manifest. Apply the pattern from Implementing Dynamic Script Loaders with Integrity.
Hash mismatch only on some users / some geographies
A CDN PoP is serving a cached version of the asset that was built before the current manifest. The classic symptom is hash mismatch for ~5–15% of users immediately after a deployment. Fix: ensure the CDN cache purge runs atomically with the manifest sync in Step 3. Use content-addressed filenames (hash in the URL) to make this problem structurally impossible.
crossorigin attribute absent — request blocked with opaque response
The browser made a no-CORS fetch for a cross-origin resource that has an integrity attribute. Per the Fetch spec, SRI cannot validate opaque responses. Add crossorigin="anonymous" to the element and confirm the server responds with Access-Control-Allow-Origin: * or the origin’s specific value.
CI gate always passes even when CDN is misconfigured
The violation rate gate in Step 5 fires only after traffic reaches the new bundle. If the gate runs during the build stage (before any users see the asset) it will always read zero violations. Move the gate to the promote/swap step that runs after canary traffic has accumulated for at least 10 minutes.
Manifest published but KV store returns stale data
Cloudflare KV has eventual consistency with up to 60 seconds of propagation delay globally. If your CDN Worker reads the manifest on every request and you purge the cache simultaneously with the KV write, some PoPs may still read the old value for up to a minute. Mitigate by writing the new manifest to KV at least 90 seconds before the cache purge fires.
Security Boundary
Permalink to "Security Boundary"CDN trust mapping enforces that bytes delivered by a CDN origin match the hash computed at build time. It does not:
- Verify the integrity of the build process itself — a compromised CI pipeline can produce a malicious bundle with a valid hash. For build provenance, use Provenance Verification Workflows.
- Protect against compromised npm packages that entered the build legitimately — SRI hashes the output bundle, not its source dependencies. Dependency-level verification requires Dependency Pinning Best Practices.
- Guard against DOM-based injection after the script loads — once the browser has verified and executed a script, SRI’s job is done. Trusted Types and CSP
script-srcrestrict runtime injection. - Prevent CDN metadata manipulation — DNSSEC records or TLS certificate substitution attacks require HTTPS certificate transparency monitoring, not SRI.
Related
Permalink to "Related"- Mapping CDN Origins to SRI Policies — audit-ready per-origin policy matrix and routing rule templates
- Static Asset Hash Generation — the upstream step that produces the hash manifest this workflow consumes
- Dynamic Script Loading Patterns — applying the same trust enforcement to runtime-injected scripts
- Browser Enforcement & Security Boundaries — the downstream enforcement layer that validates every hash the CDN delivers