Skip to content

Integrate with speculation rules - #808

Open
vickiez wants to merge 5 commits into
w3c:mainfrom
vickiez:external-spec-rules
Open

vickiez wants to merge 5 commits into
w3c:mainfrom
vickiez:external-spec-rules

Conversation

@vickiez

@vickiez vickiez commented Mar 13, 2026 •

Copy link
Copy Markdown

10/22 update: We propose adding speculation-rules-src for inline and external script-element rules while preserving the current CSP exemption for Speculation-Rules header fetches, to avoid disrupting automatic deployments. Header coverage remains an open question for the TPAC WebAppSec discussion, where we hope to agree on a direction informed by affected providers’ deployment constraints and potential compatibility impact.

The current PR diff still applies CSP to header fetches; the proposal above revises that scope and is not yet reflected in the diff. The original summary below describes the existing diff, not an agreed final plan.


Adds the following:

From #776:

  • Handling of the new "speculationrules" request destination, which is used by the Speculation-Rules HTTP header

New additions:

  • New speculation-rules-src fetch directive that restricts the sources from which speculation rules may be loaded, whether via <script> elements or the Speculation-Rules HTTP response header
  • Fallback chain: speculation-rules-src → script-src-elem → script-src → default-src, applied uniformly to both inline and external speculation rules
  • Handling of the "speculationrules" request destination now returns speculation-rules-src (removing the previous header exemption that returned null).

This reflects the agreed plan from whatwg/html#11697


Preview | Diff

domenic and others added 3 commits April 1, 2026 14:03
This upstreams the monkeypatches from https://wicg.github.io/nav-speculation/speculation-rules.html#content-security-policy. At a high level, the additions are:

- A new directive, `inline-speculation-rules`, which can be used if developers want to block inline JavaScript `<script>`s but allow inline `<script type=speculationrules>`s. This is done by introducing a new script type, `script speculationrules`, to sit alongside the existing `script` and `script attribute` types; HTML passes this new value in.

- Handling of the new `"speculationrules"` request destination, which is used by the `Speculation-Rules` HTTP header. It cannot be blocked by CSP.
@vickiez
vickiez force-pushed the external-spec-rules branch from 22806ee to 4e8aad7 Compare April 1, 2026 21:05
@vickiez
vickiez marked this pull request as ready for review April 1, 2026 21:05
@antosart

antosart commented Apr 2, 2026

Copy link
Copy Markdown
Member

We don't need the keyword inline-speculation-rules if we have speculation-rules-src, no? Developers will just be able to specify speculation-rules src 'unafe-inline'; script-src 'none' or something. I think having both is just confusing.

@hiroshige-g

hiroshige-g commented May 8, 2026 •

Copy link
Copy Markdown

IIUC (please correct me if wrong) this PR contains a major behavior changes that would break existing websites without updates to pages' CSP :
Applying speculation-rules-src CSP directive to Speculation-Rules header-initiated requests to external speculation-rules JSON files (while previously CSP is exempted per https://chromestatus.com/feature/5123809745829888).

To avoid breakage, pages should e.g. add speculation-rules-src directive that allows the requests to the JSON files if the pages have e.g. script-src CSP directives that would forbid the requests (which is likely).

cc/ @tunetheweb @nhiroki WDYT?

FYI Also see whatwg/html#11697 (comment) whatwg/html#11697 (comment) and around for previous context for direction/planning around external speculation rules.

@hiroshige-g

Copy link
Copy Markdown

Also this PR applies speculation-rules-src CSP directive instead of script-src-elem to <script type="speculationrules"> (i.e. inline speculation rules).
This would keep the existing behavior (because we'll anyway fallback to script-src-elem), if pages already have speculation-rules-src CSP directive (which is unlikely). This just opens a new option to specify speculation-rules-src without affecting more general script-src-elem.

@tunetheweb

tunetheweb commented May 8, 2026 •

Copy link
Copy Markdown
Member

Yes I thought we’d previously agreed that the HTTP Header version was out of scope? Because if you’ve the ability to set HTTP Headers then you’ve no guarantees on CSP anyway (since that is also usually set via HTTP Headers).

Also can I understand what this does for inline-speculation-rules for <script type="speculationrules">? Is this replacing that with the new speculation-rules-src: unsafe-inline directive? That would cause breakage to anyone who’s already deployed this under the old name so we need to check the web compatibility of any such change.

@annevk

annevk commented May 10, 2026

Copy link
Copy Markdown
Member

CSP should apply to links in HTTP headers as well to remain consistent.

@vickiez

vickiez commented Aug 3, 2026

Copy link
Copy Markdown
Author

Hi all, based on the latest discussion here, particularly the concern about breaking existing pages (#808 (comment)), I propose this revised plan to avoid breakage until speculation-rules-src is available. (Previous plan: whatwg/html#11697 (comment))

Stage 1 - external <script type=speculationrules> (whatwg/html#11697, whatwg/fetch#1841, CL)

  • Script element, inline and external: script-src-elem -> script-src -> default-src
  • Speculation-Rules header: exempt, unchanged from what ships today

Stage 2 - speculation-rules-src (this PR)

  • Both paths: speculation-rules-src -> script-src-elem -> script-src -> default-src
  • Pages affected by the header change can add speculation-rules-src

Note on inline-speculation-rules: inline rules would check as type script speculationrules, so an existing script-src-elem 'inline-speculation-rules' keeps working through the fallback chain. For speculation-rules-src, 'unsafe-inline' will have the same behavior.

What do you all think? If this sounds right, I can proceed with updating the specs and CL for Stage 1.

@hiroshige-g

Copy link
Copy Markdown

The incremental approach looks good to me (it's more like whatwg/html#11697 (comment) ), but I defer to @tunetheweb (speculation rules ecosystem) and @antosart (CSP) for details -- WDYT?

Comment thread index.bs Resolved
@antosart

Copy link
Copy Markdown
Member

Wrt your proposed plan, I believe it mostly applies to your implementation:

  • inline-speculation-rules is not defined in the spec, so I am not sure if you are referring to an implementation there. I believe the plan is that we won't define inline-speculation-rules in the specification.

  • The two stages approach also seems implementation specific. The spec change as is should apply both to the header and to the <script> element, assuming the hooks in fetch are correct.

If that's correct, the spec change looks fine to me modulo the comment above.

@vickiez

vickiez commented Aug 21, 2026

Copy link
Copy Markdown
Author

Wrt your proposed plan, I believe it mostly applies to your implementation:

  • inline-speculation-rules is not defined in the spec, so I am not sure if you are referring to an implementation there. I believe the plan is that we won't define inline-speculation-rules in the specification.
  • The two stages approach also seems implementation specific. The spec change as is should apply both to the header and to the <script> element, assuming the hooks in fetch are correct.

If that's correct, the spec change looks fine to me modulo the comment above.

@antosart I believe we are on the same page, we won't define inline-speculation-rules and the current spec change applies to both the header and <script> element. I created a new PR for the fetch spec here reflecting the latest changes: whatwg/fetch#1952

@tunetheweb

Copy link
Copy Markdown
Member

From my perspective this is a step backwards rather than forwards, for a number of reasons:

  • speculation-rules-src 'unsafe-inline' encourages the use of unsafe keyword. Is there a "safe" version of this (with nonce and/or hashes)? Or is the unsafe-inline the only way of using this in inline HTML? Does this mean we're effectively either encouraging the use of unsafe=inline or suggesting developers should use <script type="speculationrules" src="..."> and/or HTTP over inline HTML? Either way it seems like a regression over the current state when the aim if specifying this over it's own directive was to make it easier to deploy while allowing/not discouraging stricter script-src values.
  • It's still not clear to me why we need this for HTTP header version. Previously the thinking (at least from Google's side) was that that CSP's main scope was HTML since those with access to set HTTP headers already have the keys to the kingdom. Though I see from Add the "speculationrules" destination whatwg/fetch#1841 (comment) that that discussion remained unresolved. My main concern here is that this removes the main use case of the HTTP version of speculation rules — CDN auto-speculating for their customers. This is a huge part of speculation rules with the likes of Shopify and Cloudflare benefiting large volumes of their customers with this feature. Gating that behind CSP requires them to check and edit any existing CSP headers to add this which seems risky to me, and creates friction (which was why we decided to remove this initially, after investigating the risks and protection of what CSP realistically protects against). That's not to say that maybe should be the case, but I think it needs more discussion, perhaps with those platforms involved.

I agree <script type="speculationrules" src="..."> requires some CSP, and inline-speculation-rules seems weird to reuse for that since it's not inline, so speculation-rules-src does make sense. However, I'm not sure if we shouldn't keep inline-speculation-rules for the main use case, and have that act as a short cut of meaning speculation-rules-src 'unsafe-inline' 1) to avoid overly encouraging the use of unsafe-inline AND 2) reducing compat risk of introducing change here.

As to the whether HTTP Header Speculation Rules should be subject to CSP at all, that seems a separate conversation to what @vickiez seems interested in solving here (<script type="speculationrules" src="...">), and one we don't have agreement on yet.

@vickiez

vickiez commented Aug 28, 2026

Copy link
Copy Markdown
Author

I see what you mean, I have no objection to adding 'inline-speculation-rules' for script-src if only supporting speculation-rules-src 'unsafe-inline' encourages developers to choose other methods instead of inline speculation rules. We want to make inline speculation rules easy to allow while still supporting stricter methods, and nonces and hashes apply to inline rules in this change.

Thanks for providing the customer context for exempting the header, I’ll revive the conversation on the Fetch spec. For now, can we proceed with the speculation-rules-src addition and apply it to the HTTP header in a follow-up if we reach agreement to cover it? @tunetheweb @antosart

@tunetheweb

Copy link
Copy Markdown
Member

So some further comments/questions, since we didn't get to close this out at TPAC this week.

On-page <script type=speculation-rules> (with or without a src)

It seems, from reading the PR, that speculation-rules-src will support nonce's and hashes as well as unsafe-inline. So that answers most of my concerns in point 1 above. Please let me know if that's incorrect.

I still think there is a migration strategy necessary from inline-speculation-rules to speculation-rules-src: unsafe-inline (or hash/nonce) before we could retire inline-speculation-rules. It seems there are currently 382 origins using this at present, according to the HTTP Archive, which seems doable.

Questions on strict-dynamic

I'm not 100% clear on how all this works with strict-dynamic? If a nonce-based script is approved to run, and injects the speculation rules onto the page, then I presume those are those automatically cleared to run? Or does that also need speculation-rules-src: unsafe-inline (or hash/nonce)? What if a (potentially conflicting) speculation-rules-src directive exists — does that take precedence or does the strict-dynamic script approval bypass that? Should the answers to all these questions be specced?

The Speculation-Rules HTTP header and CSP

The Speculation-Rules HTTP header use case is still a concern for CDNs. Digging some more into on the complaints we got from Cloudflare at the time the issue (see here as one example) was for a) sites that deployed a CSP without a self option, or b) used strict-dynamic. Tagging in @SulemanAhmadd who raised this as the time (as well as @nicjansma, and @tkadlec who were in the WebAppSec room for this week's discussion).

The first a) (no self) does seem to be fairly common, from a quick check of HTTP Archive (approximately 71,436 origins from a quick query) and the main solution I see is by adding speculation-rules-src: self to the CSP. We wouldn't want to add self to script-src as that would downgrade their stricter CSP policy, which would be a step backwards. Editing CSP headers is possible, but can be error prone (especially if the page is already using speculation rules with CSP), and when done badly can even downgrade security (as the previous example shows). The feeling at the time (from the Chrome side) was just the loading speculation rules via HTTP Header was outside the treat model CSP (and certainly strict-dynamic) tries to protect against. If that was an incorrect assumption, then we must revisit this issue.

For the second b) issue (strict-dynamic), then there may be another solution (that we also discussed at the time) if the script-src also includes self. Should Speculation Rules be exempt from strict-dynamic (like other non-script resources are)? While it is a <script> element, it is a JSON-based one, rather than a JavaScript-based one and seems more-akin to other resource fetches, than arbitrary script execution (remembering of course that speculated pages will be subject to their own CSPs when those pages are speculated and loaded by the browser). Exempting that might at least reduce the implementation costs and risks for the CDN use case, since no changes would be required for sites that do include self in script-src (whether with or without strict-dynamic), providing the speculation rules are served from same-origin (which they are for Cloudflare at least anyway).

@annevk /@arturjanc / @mikewest what are your thoughts on whether strict-dynamic should apply to HTTP Speculation Rules, or whether Speculation Rules can fallback to the script-src: self option of the CSP in cases where it exists? If that was acceptable then it would still therefore be covered by CSP (so more strict than current Chromium implementation where it's completely exempt), but not as strict as the (more dangerous) arbitrary <script> case?

If that exemption was still a concern, then Cloudflare would need to again inject a speculation-rules-src: self CSP directive (to break out of script-src: strict-dynamic) and/or support nonces and hashes in the Speculation-Rules HTTP header (currently unsupported).

Alternatively, CDNs could just move away from the HTTP header completely given the CSP complications, and the rules could be injected with an on-page script instead. That could be easier with the new src attribute we're trying to add to speculation rules that kicked off this discussion again (since it could point to the same place as the HTTP Header points to now), but also potentially brings it's own CSP/security concerns and issues.

@SulemanAhmadd

Copy link
Copy Markdown

Thanks for the summary @tunetheweb. I briefly discussed this with @vickiez on a separate email thread, but wanted to bring that discussion here, primarily on CDN side concerns that we should keep in mind for a possible solution:

  1. Appending the Speculation-Rules header for CDN deployments is a zero-config operation today. Parsing streaming HTML to inject a script adds compute overhead and degrades performance metrics (for example, consider, Time to First Byte), which defeats the performance goal of the feature itself. Moving away from the HTTP header (and onto on-page script injection) will likely be a non-starter for CDN deployment architecture, especially when rules may be generated dynamically for individual origins.

  2. CSPs can be complex and are origin-specific; therefore, dynamically modifying them in-flight on the CDN can be highly error-prone (something we strictly want to avoid). Also, the threat model here does not seem clear to me: if a network actor can inject a Speculation-Rules header, they can just as easily rewrite the Content-Security-Policy header too?

  3. If header-based speculation rules are tied to script-src (inheriting strict-dynamic and nonce requirements), any CDN customer with a strict script CSP today would experience blocked fetches. Because strict-dynamic invalidates URL allowlists, these customers could not simply allowlist our injected routes; they would be forced to manually reconfigure their CSP headers to explicitly define the new speculation-rules-src directive. Requiring manual CSP reconfiguration for an auto-enabled performance feature is highly problematic at CDN scale given that the feature is auto-enabled for all sites behind Cloudflare.

I believe it is important to separate the transport mechanism from the payload. If the HTTP header itself can remain a CSP-exempt delivery mechanism (requiring no nonces), the new speculation-rules-src directive can then be used to explicitly govern the resulting network fetch. At Cloudflare, we inject the speculation rules via a same-origin URL (e.g., /cdn-cgi/speculation) so it would work as expected.

However, when speculation-rules-src is missing, should it fall back to default-src? If the fall back is to script-src, Speculation Rules can be explicitly exempt from strict-dynamic imo. Otherwise, any origin with a strict, nonce-only JS policy will immediately block the CDN-injected same-origin fetches, and as noted above, rewriting their CSP on the fly to fix it is not a viable option.

@MayyaSunil

Copy link
Copy Markdown

To address @annevk's consistency concern: header-initiated speculation rules fetches should be under CSP, but not through script-src. We discussed this during my presentation at TPAC WebPerf, and there seemed to be general agreement in WebPerf group on my proposal - give them their own directive, falling back only to default-src.

Directive Governs Fallback
speculation-rules-src <script type=speculationrules> (inline and src) → script-src-elem → script-src → default-src
speculation-rules-header-src (in loss of better name) Speculation-Rules header fetches → default-src

Precedent: CSP already picks the directive by how a resource is loaded, not by its type. Scripts loaded via new Worker() are governed by worker-src, not script-src-elem. A JSON resource the browser fetches to configure itself (the manifest) gets its own directive, with the fallback manifest-src → default-src (fallback list). Fetch would need a new initiator value for header-initiated fetches, so they can be mapped to the new directive.

Why:

  • Header fetches stay governed by CSP and report violations.
  • script-src, nonces and 'strict-dynamic' don't apply, so CDN-injected same-origin rules (e.g. /cdn-cgi/speculation) keep working.
  • If new threat vectors emerge for header-delivered rules, the dedicated directive gives sites and the spec a place to tighten restrictions without touching script-src.

what do you think @annevk?

@annevk

annevk commented Oct 29, 2026

Copy link
Copy Markdown
Member

I don't really understand why this would be different from style sheets which have exactly the same considerations? In particular they don't have a distinct directive for Link header delivery.

But bypassing CSP (or any security policy) is much worse for consistency so I guess it seems acceptable, though not ideal.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants