Skip to content

Add crossoriginstorage attribute to <link> and <script> #12770

Description

@tomayac

What problem are you trying to solve?

<link> and <script> elements load resources declaratively, driven entirely by markup, before any page script needs to run. The Cross-Origin Storage (COS) API (formal spec draft) is a proposed browser mechanism that lets resources like AI models, Wasm modules, popular JavaScript libraries, or Web fonts, be stored once and retrieved across origins, identified by their cryptographic hash rather than by URL. Today, the only way to opt a resource into COS is the (proposed) imperative navigator.crossOriginStorage.requestFileHandle() API, which has no bearing on <link>/<script> at all: a page that wants a stylesheet or script to participate in COS has to abandon markup-only loading entirely and reimplement the fetch, hash lookup, and insertion in script, losing the parser's native preloading and prioritization in the process.

This issue proposes closing that gap with a crossoriginstorage content attribute on <link> and <script>, so COS participation can be declared in markup the same way Subresource Integrity already is. A parallel CSS Working Group proposal (w3c/csswg-drafts#14056) and a companion whatwg/html issue for a crossOriginStorage import attribute share the same underlying model (see "Anything else?" below).

What solutions exist today?

The only existing (proposed) opt-in is the imperative navigator.crossOriginStorage.requestFileHandle() API. It works, but only if the page is willing to give up declarative loading for the resource entirely: instead of a plain <link>/<script> with an integrity attribute, the page has to call requestFileHandle(), read the resulting handle into a Blob, create an object URL, and point the element at that, all before the resource can load. That's extra code for every resource, it delays loading until the script that does this has run, and it does not help at all for a <script> needed at parse time, before any page script has executed.

How would you solve it?

Add a crossoriginstorage content attribute to <link> and <script>. The attribute is only meaningful when integrity is also present; integrity provides the hash that identifies the resource in COS.

The attribute value mirrors the origins option of requestFileHandle():

Value Meaning
absent No COS participation (today's behavior)
empty / valueless Same-site access only
"*" Globally available to all origins
Space-separated origin list Available to the listed origins only

A space-separated origin list is subject to the same implementation-defined maximum length as the origins option of requestFileHandle(). Without a cap, this attribute would let a page enumerate enough origins to functionally approximate "*" without ever using it, which is exactly the bypass the imperative API's cap exists to prevent (see Cross-site probing). Unlike the imperative API, a content attribute has no exception channel to reject through; an oversized (or otherwise malformed) list should most likely be treated as if crossoriginstorage were absent, rather than failing the element's fetch outright, but this is an open question (see "Anything else?" below).

Examples

Same-site only

A valueless attribute opts the resource into COS for same-site access only:

<link
  rel="stylesheet"
  href="same-site-css-framework.css"
  integrity="sha256-abc123..."
  crossoriginstorage
/>

<script
  src="same-site-js-framework.js"
  integrity="sha256-def456..."
  crossoriginstorage
></script>
Globally available
<link
  rel="stylesheet"
  href="popular-css-framework.css"
  integrity="sha256-abc123..."
  crossoriginstorage="*"
/>

<script
  src="popular-js-framework.js"
  integrity="sha256-def456..."
  crossoriginstorage="*"
></script>
Restricted to specific origins
<script
  src="acme-inc-corporate.js"
  integrity="sha256-def456..."
  crossoriginstorage="https://acme-inc.example.com https://acme-cdn.example.com"
></script>
No COS (today's behavior preserved)

Omitting crossoriginstorage entirely while keeping integrity preserves existing SRI behavior: the resource is fetched and the hash is verified, but COS is never consulted.

<script
  src="popular-js-framework.js"
  integrity="sha256-def456..."
></script>

Processing model

  1. If crossoriginstorage is absent, proceed with the existing fetch and optional SRI check. COS is not involved.
  2. If crossoriginstorage is present, before fetching from the network, the user agent checks COS for a resource matching the integrity hash. If found and the requesting origin satisfies the declared crossoriginstorage value, the resource is served from COS; no network request is made.
  3. If no COS hit, the resource is fetched from the network as usual. If the fetched content matches the integrity hash and the origin is permitted by crossoriginstorage, the user agent stores the resource in COS for future use. If the hash does not match, the resource is rejected per existing SRI behavior and is not stored in COS.

Step 2's "found" is not just presence-in-COS: even when the requesting origin satisfies crossoriginstorage, the lookup can still miss for privacy reasons: the hash may not be on the Public Hash List, or the user agent may apply GREASE'ing. This is indistinguishable from a genuine cache miss and falls through to step 3 exactly the same way; see Availability gating.

Progressive enhancement

This is naturally progressively enhancing, verified directly: an unrecognized crossoriginstorage attribute is inert content-attribute data to a browser that doesn't understand it. It reflects as a plain attribute (no IDL exposure), triggers no console warning, and has no effect on the element's normal href/src-driven fetch: the <link>/<script> loads exactly as if crossoriginstorage were never written. No feature detection, fallback markup, or authoring discipline required.

Relationship to existing attributes

  • integrity: crossoriginstorage depends on integrity being present. Without a hash there is nothing to look up in COS. User agents should ignore crossoriginstorage when integrity is absent.
  • Note on crossorigin: Entirely orthogonal. crossorigin controls the CORS request mode for the network fetch; crossoriginstorage controls COS participation.

Anything else?

Open questions:

  1. Should crossoriginstorage be silently ignored or trigger a warning when integrity is absent? (Authors' hunch: trigger a warning.)
  2. Should the attribute be valid only on specific rel values for <link> (e.g., stylesheet, preload), or on all <link> elements? (Authors' hunch: start with stylesheet, preload, and modulepreload.)
  3. What should happen when crossoriginstorage is present but the cross-origin-storage Permissions Policy feature disallows the current context? The imperative API rejects with NotAllowedError; this attribute has no exception channel, so the likely answer is to silently behave as if crossoriginstorage were absent (fetch from the network, never touch COS), consistent with an ordinary COS miss. (Authors' hunch: behave as if crossoriginstorage were absent.)
  4. Similarly, what should happen when the crossoriginstorage value itself is malformed, for example, an origin list exceeding the maximum length, or a string that fails to parse as an origin? Silently treating the element as if crossoriginstorage were absent seems most consistent with question 3's likely answer, but this should also be confirmed explicitly rather than left implicit. (Authors' hunch: behave as if crossoriginstorage were absent.)

Related proposals

Related proposals, sharing the same underlying model:

Metadata

Metadata

Assignees

No one assigned

    Labels

    addition/proposalNew features or enhancementsneeds implementer interestMoving the issue forward requires implementers to express interest

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions