skip to content

Which response header makes Subresource Integrity mandatory for a document's external scripts, and what does it block?

level: seniorimportance: nice to knowfreq 22%

answer

  1. the attribute is opt-in, per element
  2. absence of a pin is invisible
  3. a header can demand integrity metadata
  4. blocked-destinations and endpoints members
  5. the report-only twin stages it first

basics

~10 s

The Integrity-Policy response header. It is a structured-field dictionary carrying blocked-destinations and endpoints; with blocked-destinations=(script) a conforming browser blocks any external script fetched without integrity metadata, or fetched no-CORS, and reports it.

solid answer

~40 s

The `integrity` attribute is opt-in per element, so one tag added in a hurry defeats the convention silently — an unpinned script looks exactly like a healthy one. `Integrity-Policy` is the response header that inverts that. It is a **structured-field dictionary** with `blocked-destinations=(script)` naming what must be pinned and `endpoints=(...)` naming where violations go, the names resolving against a `Reporting-Endpoints` dictionary declared on the same response. Under it, an external script fetched **without integrity metadata**, or fetched in a no-CORS state so that no check was ever possible, is blocked rather than merely unpinned. Violations are reported under the report type `integrity-violation`. `Integrity-Policy-Report-Only` carries the same dictionary and blocks nothing.

code

http · 4 lines
http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Reporting-Endpoints: sri-reports="https://listings.example/reports/integrity"
Integrity-Policy: blocked-destinations=(script), endpoints=(sri-reports)

go deeper

for a junior

Know that pinning a subresource is opt-in per element, so a tag added without the attribute is unpinned and looks completely healthy from the page.

for a middle

Explain that a response header can make integrity metadata mandatory for a named destination, and that a fetch without metadata is then blocked rather than merely unchecked.

for a senior

Show the rollout: report-only first to enumerate unpinned tags, then enforcement — and read a violation report as missing metadata rather than as a changed vendor build.

for a principal

Judge the reach honestly: uneven client support makes this a raised floor rather than an invariant, and mandating a digest never mandates that anyone reviewed the build behind it.

## The gap an attribute cannot close Everything Subresource Integrity guarantees is written on an element. That is its strength — the pin travels with the tag that needs it — and it is also its structural weakness: **absence is invisible**. The classified-ads marketplace pinned its widget script two years ago and has since grown tags for a map embed, a payments script and an analytics snippet, each one line of markup that nobody compared against a convention. A source list permits all of them happily. The failure mode is asymmetric, which is what makes it dangerous: - A tag that **is** pinned and mismatches fails loudly — the feature disappears. - A tag that is **not** pinned behaves exactly like a healthy one, until the day the third party ships something nobody reviewed. No amount of care in review turns an opt-in attribute into an invariant. `Integrity-Policy` moves the requirement off the element and onto the response that carried the document. ## The header The field is a **structured-field dictionary** — the grammar family defined by `RFC 9651` — with two members that matter: - **`blocked-destinations`** — an inner list of the fetch destinations the requirement applies to. `blocked-destinations=(script)` applies it to script. - **`endpoints`** — an inner list of endpoint **names**. A name is not a URL; it resolves against a `Reporting-Endpoints` dictionary declared on the same response, which is where the URL lives. A second field, **`Integrity-Policy-Report-Only`**, carries exactly the same dictionary and enforces nothing at all. It reports and lets the fetch proceed. ## What is actually blocked Two cases, and the second is the one people miss: 1. **An external script fetched with no integrity metadata.** The ordinary case — a tag added without the attribute. 2. **An external script fetched in the no-CORS state.** A cross-origin fetch with no `crossorigin` attribute yields an opaque response that could never be validated, so it cannot satisfy a policy demanding a check even when an `integrity` attribute is present on the element. That second case closes a real loophole: a tag that looks pinned in review, is not, and already fails to load for reasons nobody diagnosed. Under the header it becomes a reported violation instead of a mystery. ## Reporting Violations surface under the report type `integrity-violation`, delivered to the endpoint named in `endpoints`. Two things to hold onto when reading the feed: - An `integrity-violation` means **metadata was missing, or the fetch was not CORS**. It is not a digest mismatch; a mismatch is an ordinary load failure and never appears here. Confusing the two sends you hunting a changed vendor build when the real cause is a template emitting an unpinned tag. - The report names which fetch was unprotected, which is the entire operational point. ## Staging it The enforcing header on a site with any history will break something, so the order is: 1. Ship `Integrity-Policy-Report-Only` with an endpoint. Nothing changes for users. 2. Read the feed until it names a finite, known set of tags. 3. Pin each one, or drop the partners that will not answer a cross-origin read and therefore cannot be pinned at all. 4. Move the dictionary onto `Integrity-Policy`. A future tag added without a pin now fails during development rather than shipping unnoticed. ## Which mechanism answers which question | mechanism | enforces | answers | |---|---|---| | `Content-Security-Policy` | which hosts a resource may be fetched from | where may bytes come from | | `Integrity-Policy` | that a fetch carries integrity metadata and is CORS | must these bytes be pinned at all | | the `integrity` attribute | which bytes are acceptable for this element | which exact bytes | The three compose rather than overlap. An allowlisted host is still a third party; a mandated pin is still only as good as the digest written into the tag; and a digest is only compared when the fetch was eligible. ## What it still does not do - **It pins nothing itself.** It requires that *something* pins, and the digests still come from the elements. An engineer under deadline can paste the digest of whatever the partner serves today, which satisfies the header completely and reviews nothing. - **It covers the destinations you listed**, not every subresource a document fetches. - **It says nothing about origins.** A file can be perfectly allowlisted and still blocked here for arriving unpinned. - **It is a request-time control, not a build-time one.** It tells you that something shipped unpinned; catching that before it ships is a different control in a different place. The honest argument for the header is organisational rather than cryptographic. On a site old enough to have accumulated partner tags from several teams, the failure is never "our digest was wrong" — it is "nobody knew that tag was there".

  • Does an integrity-violation report mean a digest did not match?
    No. It means the fetch carried no integrity metadata, or was made in a no-CORS state and so could never be validated. A genuine digest mismatch is an ordinary load failure and does not appear in this feed, so reading the two as the same thing points the investigation at the vendor instead of at your own template.
  • Why is a script with an integrity attribute but no crossorigin attribute still a violation here?
    Because the fetch is in the no-CORS state, so the response would be opaque and the metadata could never be checked. The header treats that as unpinned, which is honest: such a tag looks pinned in review, fails to load in practice, and is usually misdiagnosed as a partner outage.
  • Does enforcing this header mean the page's third-party code is now trustworthy?
    No. It means every external script in the covered destinations is pinned to *some* digest. Whether those digests correspond to code anyone examined is a separate question the header cannot answer, and uneven client support means it raises the floor rather than establishing an invariant.

saying these in an interview costs you the question

  • Thinks the header pins hashes itself rather than demanding them
  • Believes Integrity-Policy-Report-Only blocks the fetches it reports
  • Assumes a no-CORS fetch satisfies a policy that demands a check
  • Thinks a host allowlist already requires integrity metadata
  • Reads an integrity-violation report as a digest mismatch