A third-party `<script>` carrying an `integrity` attribute never executes and no digest mismatch is reported — which missing attribute explains it?
answer
- the check needs readable bytes
- an opaque response cannot be digested
- integrity requires a cors-mode fetch
- crossorigin sets mode and credentials mode
- anonymous keeps credentials mode same-origin
basics
~20 sThe crossorigin attribute. Subresource Integrity needs a cors-mode fetch for a cross-origin file; without crossorigin the request stays in the no-cors state, the response is opaque, and a conforming browser blocks it before any digest is compared.
solid answer
~40 sSubresource Integrity requires CORS, and the `crossorigin` attribute is what supplies it. With no attribute, a cross-origin subresource request is made in the no-cors state and the response comes back opaque — its bytes are never exposed, so there is nothing to digest, and the client blocks the resource rather than checking anything. That is why the failure reads as "never loaded" rather than "hash mismatch". `crossorigin="anonymous"` puts the request in cors mode with credentials mode same-origin; `crossorigin="use-credentials"` puts credentials mode on include. Either way the widget host must now answer with a CORS grant, so pinning a third-party file is a change on both sides. A same-origin subresource needs none of this: its response is not opaque and is checked as written.
code
html · 8 lines<!-- blocked: no crossorigin, so the fetch is no-cors and the response is opaque -->
<script src="https://widgets.ads-partner.test/listing-carousel.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"></script>
<!-- checkable: request mode cors, credentials mode same-origin -->
<script src="https://widgets.ads-partner.test/listing-carousel.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>go deeper
Remember that the two attributes travel together on a cross-origin tag. An integrity attribute without crossorigin is the single most common reason a pinned widget silently fails to load.
Explain the mechanism: no-cors gives an opaque response, opaque bytes cannot be digested, so the client blocks rather than checks. Be able to say what each crossorigin value sets on the request.
Diagnose the three identical-looking failures without guessing — attribute missing, grant missing, digest wrong — and say which side fixes each. Note that pinning changes the request the partner host receives.
The call is whether a vendor who will not grant cors-mode reads on their asset host is a dependency you can pin at all, and whether self-hosting a reviewed copy is the cheaper guarantee across the estate.
## The failure, exactly The classified-ads marketplace adds a digest to the third-party widget tag, deploys, and the widget is simply gone. Nothing complains about a hash. That is the signature of the wrong failure being diagnosed: the resource never got as far as being compared, because it was refused one step earlier. The specification states it outright — **Subresource Integrity requires CORS**, and using the attribute on a cross-origin subresource without CORS is a logical error rather than a subtle risk. The reason is mechanical. A cross-origin subresource request made with no `crossorigin` attribute is issued in the **no-cors** state, and a no-cors response is **opaque**: the client will use it for its side effect but never exposes its body. A body you cannot read is a body you cannot digest, so the check the attribute demands cannot run at all, and a conforming client blocks the resource rather than loading it unchecked. ## What the crossorigin attribute sets `crossorigin` is an HTML content attribute with an enumerated value — markup, not a scripting interface — and each value moves two separate knobs: the request's **mode** and its **credentials mode**. | markup | request mode | credentials mode | checkable on a cross-origin file | |---|---|---|---| | attribute absent | no-cors | — | no; the response is opaque and the resource is blocked | | `crossorigin="anonymous"` | cors | same-origin | yes | | `crossorigin="use-credentials"` | cors | include | yes, if the grant covers a credentialed request | Two consequences fall out of that table and both get missed: - **`anonymous` is not a weakened setting; it is the ordinary one.** It asks for cors mode while leaving credentials mode at same-origin, which for a cross-origin request means the widget host receives no ambient credentials. That is what you want for a public asset on someone else's host. - **`use-credentials` is a deliberate escalation.** It moves credentials mode to include, and the third party's grant then has to cover that mode. Reaching for it because "the file would not load" almost always means the real problem was the missing grant, not missing credentials — and a genuinely per-user body has a per-user digest, so it cannot be pinned in static markup anyway. ## The change is on both sides Moving to cors mode means the widget host is being asked a question it was never asked before. It has to answer with a CORS grant naming the requesting origin — an `Access-Control-Allow-Origin` value it is willing to send for that file. A vendor whose asset host has only ever been fetched no-cors may send no grant at all, and then the pinned tag fails for a reason that has nothing to do with your digest. So pinning a third-party subresource is a **three-party agreement**: 1. The policy's source list must permit the host. 2. The host must answer the cors-mode fetch with a grant. 3. The bytes must match one of the digests you wrote. All three must hold, any one of them failing leaves the page without the widget, and only the third produces anything that mentions a digest. ## Same-origin subresources None of this applies to a file the site serves itself. A same-origin subresource response is not opaque, so the body is available, the digest is computed, and the comparison runs with no `crossorigin` attribute present. This is why the attribute pair feels arbitrary when someone first meets it locally, where everything is same-origin, and then becomes mandatory the moment the same tag points at a third party. ## Reading the failure When a pinned third-party subresource does not appear, separate the three causes before touching the digest: - **No `crossorigin` attribute on the element.** The request went out no-cors and was blocked before any comparison. Nothing in the failure will mention a hash. - **`crossorigin` present, no grant from the host.** The fetch is a cors-mode request the host declined to authorise, so it fails as a cross-origin failure — again with no hashing involved. - **Both present, digest does not match.** Now, and only now, is the integrity check the thing that failed, which means the file genuinely is not the one you pinned. Fetch the file outside the browser and hash what the host actually serves. The practical rule is to write the two attributes as a pair and treat a lone `integrity` on a cross-origin element as a defect on sight. It is not a hardening step that failed to apply; it is a tag that cannot load. And adding a digest is therefore never purely additive — it obliges you to change the request the third party receives.
- Why does the same integrity attribute work on a first-party file with no crossorigin attribute?Because a same-origin subresource response is not opaque. Its body is available to the client, so the digest is computed and compared as written. The `crossorigin` attribute exists to get a readable response out of a *cross-origin* fetch, which is the only case where the no-cors state hides the bytes.
- Why would allowing the check on an unreadable cross-origin response be dangerous?It would turn the check into a read oracle. A hostile page could embed a resource it may not read, supply a guessed digest, and learn from whether the load succeeded whether the guess was right. Requiring CORS means the page could have read the body anyway, so the comparison leaks nothing new.
- The widget host answers the cors-mode request and the tag still fails. Where do you look next?Now the digest is the live suspect. Confirm it was taken over the exact bytes that host serves for that URL — not a local copy, not a differently compressed or rewritten build, and not a mutable "latest" URL whose content changed after you measured it.
saying these in an interview costs you the question
- Says the client falls back to an unchecked load without crossorigin
- Thinks crossorigin anonymous sends cookies to the third-party host
- Assumes integrity metadata by itself puts the fetch in cors mode
- Reads the missing attribute as a digest-mismatch failure
- Believes a same-origin file also needs the attribute to be checked