Combining require-sri-for with CSP

Permalink to "Combining require-sri-for with CSP"

Part of Coordinating SRI, CSP & Trusted Types, this page shows how to combine the require-sri-for CSP directive with a source-based policy and integrity attributes — and, just as importantly, why you must not depend on that directive alone.

Quick Reference

Permalink to "Quick Reference"
Property Value
Directive require-sri-for
Valid tokens script, style, script style
Header Content-Security-Policy (or -Report-Only)
What it enforces Every matching element must carry an integrity attribute
What it does not do Verify the hash value — that is SRI’s own job
Companion attribute integrity="sha384-…" + crossorigin="anonymous"
Preferred algorithm sha384
Browser support Shipped in Chromium behind a flag, then removed; not reliable in Chrome, Edge, Firefox, or Safari
Practical enforcement Build-time integrity check + source-based script-src

require-sri-for expresses a genuinely useful rule — “refuse any script or style that has no integrity attribute” — but its support story means it can only ever be a bonus layer. Treat build-time enforcement as the mechanism that actually holds, and this directive as an extra net for engines that honour it.

What require-sri-for Was Meant to Do

Permalink to "What require-sri-for Was Meant to Do"

The integrity attribute is opt-in per element: if a tag omits it, the browser loads the resource with no verification at all. require-sri-for was designed to close that gap at the policy level — with require-sri-for script style active, a compliant browser refuses to load any <script> or <link rel="stylesheet"> that lacks an integrity attribute, including tags injected at runtime by analytics loaders or tag managers. It turns SRI from advisory to mandatory for a whole resource class.

The catch is history. The directive shipped in Chromium behind the experimental web platform features flag, saw limited adoption, and was subsequently removed; it never reached stable, cross-engine support in Chrome, Edge, Firefox, or Safari. So the rule it expresses is sound, but the browser can no longer be trusted to enforce it. The practical path is to enforce integrity coverage in your build pipeline — where you control it deterministically — and to pair it with a source-based Configuring Content Security Policy with SRI policy that blocks unauthorized sources on every engine.

How the Directive Sits Alongside script-src

Permalink to "How the Directive Sits Alongside script-src"

It helps to be precise about the division of labour, because require-sri-for and script-src are easy to conflate. script-src answers which sources may load: a script from a host outside the allow-list, or an inline script without the matching nonce, is refused before a single byte is fetched. require-sri-for answers a different question — must this element be integrity-checked at all — and only for the resource classes you name (script, style, or both). A tag can satisfy script-src (right host, valid nonce) yet still be one an attacker injected without an integrity attribute; that is the gap require-sri-for was meant to close, and the reason it is worth expressing even though the browser rarely honours it today.

The two directives never conflict, so there is no downside to shipping both: script-src provides the enforcement that actually holds across engines, while require-sri-for documents intent and adds a bonus check for any engine — present or future — that implements it. The order of the tokens in the header is irrelevant; the browser evaluates each directive independently. The broader picture of how these directives compose with Trusted Types in a single response is laid out in Coordinating SRI, CSP & Trusted Types.

Canonical Implementation

Permalink to "Canonical Implementation"

The response below combines all three pieces in one HTTP exchange: a source-based script-src, the require-sri-for directive as a bonus layer, and integrity attributes on the markup. This is the complete pattern to copy.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-r4nd0mBase64==' https://cdn.example.com;
  style-src 'self' https://cdn.example.com;
  require-sri-for script style;
  object-src 'none';
  base-uri 'none';
  report-to csp-endpoint
<!-- Same response, markup layer -->
<script
  src="https://cdn.example.com/app.4f9c2a.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux8JHrklT1RNnZ3d8l6+pNnS3lPmW2"
  crossorigin="anonymous"></script>

<link
  rel="stylesheet"
  href="https://cdn.example.com/app.9a1b3c.css"
  integrity="sha384-3cskD8shRqChMKCMlMNPezEwv0GFHsT7OxGnbFPeK4mvPkVGKpkB7Vp0jLvIFRE"
  crossorigin="anonymous">

The crossorigin="anonymous" attribute is mandatory on both cross-origin tags: without it the browser issues a no-CORS fetch, receives an opaque response it cannot read, and the integrity check never runs — the resource is blocked or, worse, loaded unverified depending on context. On every engine, the script-src nonce and host allow-list do the real blocking of unauthorized sources; require-sri-for adds the “must be integrity-checked” rule wherever it is honoured.

Variant Examples

Permalink to "Variant Examples"

Build-time enforcement — the mechanism you actually rely on

Permalink to "Build-time enforcement — the mechanism you actually rely on"

Because the browser directive is unreliable, fail the build when any script or stylesheet lacks an integrity attribute. This is deterministic and engine-independent.

// scripts/check-sri-coverage.mjs
// Run: node scripts/check-sri-coverage.mjs dist/index.html
import { readFileSync } from 'node:fs';

const html = readFileSync(process.argv[2], 'utf8');
const tags = html.match(/<(script|link)\b[^>]*>/gi) ?? [];

const offenders = tags.filter((tag) => {
  const isExternal = /\b(src|href)=["']https?:\/\//i.test(tag);
  const isStylesheet = /rel=["']stylesheet["']/i.test(tag);
  const isScript = /^<script/i.test(tag);
  if (!(isExternal && (isScript || isStylesheet))) return false;
  return !/\bintegrity=["']sha384-/i.test(tag) || !/\bcrossorigin=/i.test(tag);
});

if (offenders.length) {
  console.error('Tags missing sha384 integrity or crossorigin:');
  offenders.forEach((t) => console.error('  ' + t));
  process.exit(1);
}
console.log('All external script/style tags carry sha384 integrity + crossorigin.');

Report-Only rollout of the source policy

Permalink to "Report-Only rollout of the source policy"

Before enforcing, ship the source policy in Report-Only so you can observe which tags would break without blocking anyone:

Content-Security-Policy-Report-Only:
  script-src 'self' 'nonce-r4nd0mBase64==' https://cdn.example.com;
  require-sri-for script style;
  report-to csp-endpoint

SHA-512 for high-assurance pages

Permalink to "SHA-512 for high-assurance pages"

Where a payment or admin surface warrants the larger margin, swap the algorithm — the directive and CSP are unchanged:

<script
  src="https://cdn.example.com/checkout.7c1f0e.js"
  integrity="sha512-Q2m9V0m0m1n0p2q3r4s5t6u7v8w9x0y1z2A3B4C5D6E7F8G9H0I1J2K3L4M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8a9b0c1d2e3f4g5h6i7"
  crossorigin="anonymous"></script>

Scoping the directive to scripts only

Permalink to "Scoping the directive to scripts only"

If your stylesheets are all first-party and same-origin, you may scope the directive to scripts alone. This is the minimum surface for the PCI DSS 6.4.3 script-integrity expectation while avoiding false positives on inline <style> blocks:

Content-Security-Policy:
  script-src 'self' 'nonce-r4nd0mBase64==' https://cdn.example.com;
  require-sri-for script;
  report-to csp-endpoint

Extend it to require-sri-for script style only once every cross-origin stylesheet also carries a sha384 integrity attribute, or compliant engines will refuse those links.

Gotchas and Edge Cases

Permalink to "Gotchas and Edge Cases"
  • require-sri-for has limited and removed browser support — never rely on it alone. It shipped behind a Chromium flag and was later removed; current Chrome, Edge, Firefox, and Safari do not reliably enforce it. If it is your only mechanism, an injected integrity-less tag loads freely. Enforce coverage at build time and block sources with script-src; keep the directive only as a bonus layer.
  • Omitting crossorigin="anonymous" silently defeats SRI. This is the most common real-world bug. Without it the browser makes a no-CORS request, gets an opaque response whose bytes it cannot hash, and the integrity check cannot run. Every cross-origin <script> and <link> with an integrity attribute must also carry crossorigin="anonymous", or the pairing with require-sri-for is meaningless.
  • The directive checks presence, not correctness. require-sri-for only asserts that an integrity attribute exists. A tag with a wrong hash still fails SRI verification separately; a tag with a present but attacker-supplied hash passes the directive. Presence enforcement and value verification are two different checks.
  • A static nonce equals no nonce. If you pair require-sri-for with a nonce-based script-src, the nonce must be regenerated per response. A cached page with a fixed nonce is functionally unsafe-inline and undermines the whole source layer.
  • Report-Only never blocks. require-sri-for inside a Content-Security-Policy-Report-Only header only reports; it does not refuse the resource. Confirm you have promoted to the enforcing Content-Security-Policy header before assuming any blocking behaviour — then remember blocking still depends on engine support.

Verification Steps

Permalink to "Verification Steps"

1. Confirm every external tag carries integrity + crossorigin

Permalink to "1. Confirm every external tag carries integrity + crossorigin"
grep -Eo '<(script|link)[^>]*https?://[^>]*>' dist/index.html \
  | grep -v 'integrity=' && echo "FAIL: tag(s) above lack integrity" || echo "PASS: all external tags have integrity"

Expected output:

PASS: all external tags have integrity

2. Confirm the composed header is served

Permalink to "2. Confirm the composed header is served"
curl -sI https://app.example.com/ | grep -i 'content-security-policy'

Expected output includes both the source directive and the belt-and-suspenders directive:

content-security-policy: default-src 'self'; script-src 'self' 'nonce-…' https://cdn.example.com; require-sri-for script style; …

3. Confirm real enforcement does not depend on the directive

Permalink to "3. Confirm real enforcement does not depend on the directive"

Because require-sri-for may be a no-op, verify the source policy blocks an unauthorized tag on your target engines. Inject a nonce-less script in a test and expect a script-src refusal in the console:

Refused to load the script 'https://evil.example/x.js' because it violates
the following Content Security Policy directive: "script-src 'self' 'nonce-…'".

4. Gate the build on SRI coverage

Permalink to "4. Gate the build on SRI coverage"
# GitHub Actions
- name: Enforce SRI coverage
  run: node scripts/check-sri-coverage.mjs dist/index.html

A non-zero exit blocks the deploy before an integrity-less tag can ever reach production — the enforcement guarantee the browser directive can no longer provide.

Permalink to "Related"

Related Articles

Deploying Defense-in-Depth Script Controls
Coordinating SRI, CSP & Trusted Types Runtime Policy Enforcement & T…