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.

Anatomy of require-sri-for The directive is split into three parts: the name require-sri-for, which shipped behind a Chromium flag and was removed; the token script, which applies to every script element; and the token style, which applies to every stylesheet link. A note explains that the directive only asserts that an integrity attribute exists, while SRI separately verifies the hash value. Directive as written in the Content-Security-Policy header require-sri-for script style Directive name shipped behind a flag, removed Token: scripts every script tag Token: stylesheets every link rel=stylesheet Presence check only: the directive asserts that an integrity attribute exists. SRI itself still verifies the hash value.

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.

Which check catches which tag A four-row matrix. A tag from an allowed host with integrity passes all three checks. A tag from an allowed host without integrity passes script-src, is refused by require-sri-for where honoured, and is never hash-verified. A tag from an unlisted host is refused by script-src before the other checks are reached. An injected inline tag without a nonce is refused by both source and presence checks. Tag scenario script-src (source check) require-sri-for (where honoured) SRI integrity (hash check) Allowed host, integrity present allowed satisfied hash verified Allowed host, integrity missing allowed refused never runs Unlisted host, integrity present refused not reached not reached Injected inline tag, no nonce refused refused not reached

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. If you are assembling the whole header rather than one directive, Layering CSP Nonces, SRI and Trusted Types works through the ordering and precedence of every script control in one response. 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.

Where enforcement actually happens Four stages run left to right: the build gate that fails on a missing integrity attribute, the deploy step that freezes artifact hashes, the browser source check performed by script-src, and the browser presence check performed by require-sri-for. Only the build gate and the script-src check are enforced everywhere; the deploy stage enforces nothing and require-sri-for is not enforced by shipping browsers. Where the enforcement actually happens 1. Build gate fails on missing integrity 2. Deploy artifact hashes frozen 3. script-src blocks unlisted sources 4. require-sri-for presence check, engine-dependent deterministic runs every build no enforcement transport only enforced on all current engines not enforced by shipping browsers Stages 1 and 3 hold on every engine; stage 4 is a bonus layer only.
// 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. The staged approach — collect reports, fix the offenders, then promote the header — is covered end to end in Rolling Out a Script Policy in Report-Only Mode:

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.

Frequently Asked Questions

Permalink to "Frequently Asked Questions"
Is require-sri-for still supported in Chrome?

No. require-sri-for shipped in Chromium behind the experimental web platform features flag and was later removed. It is not reliably available in current Chrome, Edge, Safari, or Firefox, so it must never be your sole enforcement mechanism. Enforce integrity coverage at build time and pair it with a source-based CSP.

If require-sri-for is unreliable, why include it at all?

As defence in depth. Where a browser or a future re-implementation honours it, it refuses any script or style element that lacks an integrity attribute, catching dynamically injected tags your build check could not see. It costs a few bytes in the header and never weakens the policy, so it is safe to include as long as real enforcement lives elsewhere.

Does require-sri-for cover images, fonts, or fetch requests?

No. The only valid tokens are script and style, because those are the only element types SRI itself covers: <script> elements and <link> elements with rel of stylesheet, preload, or modulepreload. Images, media, XHR and fetch responses have no integrity attribute to require, and Why Font Files Cannot Carry an integrity Attribute explains the equivalent gap for @font-face. Those resource classes are governed by img-src, font-src and connect-src instead.

How can I tell whether a browser honoured the directive?

There is no feature-detection API for individual CSP directives, so test it empirically. Serve the enforcing header on a scratch page that loads one external script deliberately missing its integrity attribute. If the engine honours require-sri-for the script is refused and a violation report names it as the effective directive. If the script loads normally the engine parsed the directive and ignored it, which is the outcome you should currently expect.

Permalink to "Related"

Related Articles

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