Configuring Content-Security-Policy with SRI

Permalink to "Configuring Content-Security-Policy with SRI"

Part of Browser Enforcement & Security Boundaries, this page covers exactly how to align Content-Security-Policy header directives with integrity attributes so the browser enforces both origin allowlisting and payload verification simultaneously.

Quick reference

Permalink to "Quick reference"
Item Value
Header Content-Security-Policy
Relevant directives script-src, style-src, default-src, script-src-elem, style-src-elem
Required tag attribute (cross-origin) crossorigin="anonymous"
Hash algorithm (recommended) sha384
Fallback / monitoring mode Content-Security-Policy-Report-Only
Browser support (SRI + CSP) Chrome 45+, Firefox 43+, Safari 11.1+, Edge 17+

Why these two controls must be configured together

Permalink to "Why these two controls must be configured together"

CSP and SRI defend against different segments of a supply chain attack. CSP restricts which origins the browser will contact at all; SRI verifies that the bytes delivered from those origins match a known-good cryptographic hash. A compromised CDN that still serves assets from an allowed origin defeats CSP alone — the browser receives the tampered file without complaint. SRI catches the mismatch. Omitting either layer leaves a gap:

  • CSP without SRI: an attacker who gains write access to an allowed CDN origin can inject arbitrary code.
  • SRI without CSP: a cross-site script injection can load an external file from an attacker-controlled origin, bypassing integrity checks entirely.

Together they create a two-factor trust model: the browser checks the source and the content of every external asset. This interaction is part of the broader Core SRI Fundamentals & Browser Security Boundaries enforcement model.


CSP and SRI two-layer enforcement flow A browser fetch passes through two sequential gates: CSP origin check, then SRI hash comparison. A failure at either gate blocks the resource. Browser fetch() Gate 1 CSP origin check (script-src / style-src) Blocked Network error Gate 2 SRI hash check (integrity attribute) Blocked Integrity error Executed ✓ safe

Canonical implementation

Permalink to "Canonical implementation"

The following HTML fragment shows the complete, production-ready pattern. Both attributes are required on every cross-origin resource; removing either one degrades the security guarantee.

<!-- External script from a CDN with CSP + SRI -->
<script
  src="https://cdn.example.com/libs/framework-3.2.1.min.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux6EYkFGCO3BNLZ6TDkxJLGfm1yPOD"
  crossorigin="anonymous"
  referrerpolicy="no-referrer"
></script>

<!-- External stylesheet with CSP + SRI -->
<link
  rel="stylesheet"
  href="https://cdn.example.com/css/tokens-2.0.0.min.css"
  integrity="sha384-AbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEf123456"
  crossorigin="anonymous"
/>

The matching CSP header allows exactly those CDN origins and nothing more:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src  'self' https://cdn.example.com;
  report-uri /csp-report

crossorigin="anonymous" is not optional. Without it the browser issues a no-cors request and receives an opaque response — a body the browser deliberately withholds from JavaScript — making hash comparison impossible. The resource is blocked regardless of whether the integrity hash is correct.

Variant examples

Permalink to "Variant examples"

1. Nonce for server-rendered inline scripts

Permalink to "1. Nonce for server-rendered inline scripts"

When your server-side template renders critical inline configuration, a per-request nonce allows that script to execute without using unsafe-inline. Generate a cryptographically random value for each HTTP response:

import secrets, base64

nonce = base64.b64encode(secrets.token_bytes(18)).decode()
# e.g. "rAnd0mBase64StringHere=="

Inject the nonce into both the header and the tag:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-rAnd0mBase64StringHere==' https://cdn.example.com;
<script nonce="rAnd0mBase64StringHere==">
  window.__APP_CONFIG__ = { apiBase: "/api/v2" };
</script>

Nonces and integrity attributes serve separate purposes and can appear on the same tag for inline scripts you hash ahead of time. For external files, integrity alone is the right tool.

2. strict-dynamic for module graph loading

Permalink to "2. strict-dynamic for module graph loading"

When a trusted entry-point script dynamically imports further modules (common in ES module architectures), strict-dynamic propagates trust to those child imports without listing each origin explicitly:

Content-Security-Policy:
  script-src 'strict-dynamic' 'nonce-rAnd0mBase64StringHere==' https:;

The entry-point script carries the nonce. All scripts it loads programmatically inherit trust. Add integrity checks on the entry point itself for the payload-verification benefit:

<script
  type="module"
  src="https://cdn.example.com/app/entry.js"
  integrity="sha384-Xy1Pq2Rs3Tv4Uw5Vx6Wy7Xz8Ya9Zb0Ac1Bd2Ce3Df4Eg5Fh6Gi7Hj8Ik9Jl0Km"
  crossorigin="anonymous"
  nonce="rAnd0mBase64StringHere=="
></script>

3. Nginx server-side header injection

Permalink to "3. Nginx server-side header injection"

Inject the header at the reverse proxy layer so it applies to every response without touching application code:

server {
    listen 443 ssl;
    server_name app.example.com;

    add_header Content-Security-Policy
      "default-src 'self'; \
       script-src 'self' https://cdn.example.com; \
       style-src  'self' https://cdn.example.com; \
       font-src   'self' https://cdn.example.com; \
       img-src    'self' data: https://cdn.example.com; \
       report-uri /csp-report"
      always;
}

The always flag ensures the header is sent on error responses (4xx, 5xx) as well as success responses — without it browsers receive no policy on error pages and can load arbitrary scripts from them.

For edge-layer injection with CDN Trust Mapping & Routing, adapt the same directive set into a Cloudflare Worker Response constructor or an AWS CloudFront response policy.

Gotchas and edge cases

Permalink to "Gotchas and edge cases"
  • Missing crossorigin="anonymous" on external resources. This is the single most common SRI deployment error. An external <script> or <link> without crossorigin="anonymous" fetches in no-cors mode. The browser cannot compare the opaque-response body against the integrity hash and blocks the resource — even if your CSP header explicitly allows the origin. Always add the attribute; it has no negative effect on same-origin resources.

  • Using unsafe-inline negates CSP enforcement for scripts. The unsafe-inline keyword permits any inline <script> block on the page, including attacker-injected ones. It defeats the key purpose of pairing CSP with SRI. Replace unsafe-inline with nonces or inline script hashes (note: inline script hashes are separate from external-file integrity attributes and use sha256-... syntax directly in the script-src directive).

  • Duplicate CSP headers concatenate unpredictably. If your application server and your reverse proxy both set Content-Security-Policy, browsers receive two headers. The effective policy is the intersection (the most restrictive of each directive across both headers). This can silently block resources that appear allowed in one header. Centralise header injection at one layer and audit with curl -I to confirm a single header is returned.

  • Hardcoding hashes in static templates breaks on every asset update. An asset update changes the hash. If you embed the integrity value by hand, each deployment to a new asset version requires a manual template edit. Automate hash injection via Automating Hash Generation in Webpack 5 or equivalent build tooling so the attribute is always in sync with the bytes on disk.

  • require-sri-for is not yet broadly supported. The require-sri-for script directive would instruct the browser to reject any external script lacking an integrity attribute. As of mid-2026 it is still an experimental directive removed from the CSP Level 3 spec draft pending redesign. Do not rely on it for production enforcement; use integrity attributes explicitly on every external tag instead.

  • Report-URI vs Report-To endpoint. report-uri sends a POST to a single URL and is universally supported. report-to uses the newer Reporting API and requires a Reporting-Endpoints response header to define endpoint groups. Deploy report-uri first; add report-to later when you have the Reporting API infrastructure in place. For Graceful Fallback Strategies to work, you must have violation data flowing before cutover to enforcement mode.

Verification steps

Permalink to "Verification steps"

1. Confirm the header in a real response

Permalink to "1. Confirm the header in a real response"
curl -sI https://app.example.com/ | grep -i content-security-policy

Expected output — a single Content-Security-Policy header with your directives. A second header on the next line indicates a duplication problem.

2. Confirm integrity attributes appear in built HTML

Permalink to "2. Confirm integrity attributes appear in built HTML"
grep -E 'integrity="sha384-' dist/index.html

Every external <script> and <link rel="stylesheet"> should appear in the output. A missing line means your build tooling is not injecting hashes.

3. Trigger a deliberate mismatch in a staging environment

Permalink to "3. Trigger a deliberate mismatch in a staging environment"

Change one character of an integrity attribute value in a staging HTML file, open the page in Chrome DevTools, and confirm the console shows:

Failed to find a valid digest in the 'integrity' attribute for resource
'https://cdn.example.com/libs/framework-3.2.1.min.js' with computed
SHA-384 integrity 'oqVuAfXRKap7fdg...'. The resource has been blocked.

If you do not see this message, the crossorigin attribute is likely missing, or the resource is served same-origin (which does not require CORS).

4. Run in Report-Only mode before enforcement

Permalink to "4. Run in Report-Only mode before enforcement"

Deploy with Content-Security-Policy-Report-Only and the same directive set for at least 72 hours. Aggregate violation reports at your report-uri endpoint. Any blocked-uri entries in the violation payloads indicate origins or inline scripts not yet accounted for in your policy. Address every violation before switching to Content-Security-Policy.

5. CI gate: fail the build on missing integrity attributes

Permalink to "5. CI gate: fail the build on missing integrity attributes"
# Fail CI if any external script or stylesheet link lacks an integrity attribute
if grep -E '<(script|link)[^>]+src=|href=' dist/index.html | grep -v 'integrity='; then
  echo "ERROR: external resource missing integrity attribute" >&2
  exit 1
fi

Add this check to your deployment pipeline alongside the Understanding Cryptographic Hash Algorithms validation step to catch regressions before production.


Permalink to "Related"

Related Articles

Debugging SRI Hash Mismatch Errors
Browser Enforcement & Security Boundaries Core SRI Fundamentals & Browse…