whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
8.11k stars 2.67k forks source link

"Default fetch and process the linked resource" does not account for CORS cross-origin fetches, thus preventing cross-origin stylesheets #9066

Open Lubrsi opened 1 year ago

Lubrsi commented 1 year ago

https://html.spec.whatwg.org/multipage/semantics.html#fetching-and-processing-a-resource-from-a-link-element:concept-fetch

Fetch request with processResponseConsumeBody set to the following steps given response response and null, failure, or a byte sequence bodyBytes:​

Let success be true.

If either of the following conditions are met:​

    bodyBytes is null or failure; or

    response's status is not an ok status,

then set success to false.

CORS cross-origin responses in the No CORS request mode provide an opaque filtered response, which is the original response with certain attributes removed/changed.

The relevant effect it has is setting the body to null, which means body_bytes is null in the processResponseConsumeBody callback. This effectively disables cross-origin linked resources (e.g. stylesheets).

However, the web actually depends on this, especially for stylesheets retrieved from a cross-origin CDN. For example, Shopify websites request stylesheets from cdn.shopify.com and Substack websites request stylesheets from substackcdn.com.

We worked around this in LibWeb here: https://github.com/SerenityOS/serenity/pull/17997

The workaround is to read the actual body from the unfiltered response and then call process_linked_resource from there.

This should be safe to do, as linked resource fetches do not include credentials (i.e. cookies and the Authorization header), so it cannot provide personalized responses.

We first spotted this with https://twinings.co.uk/, which is a Shopify website requesting stylesheets from https://cdn.shopify.com

annevk commented 1 year ago

This is https://github.com/whatwg/fetch/issues/1512.

There's another aspect here though that I forgot about. Response's status will be masked as well. So even if we modify Fetch to hand HTML the full response, it would still have to dig into the internal response to get the status out.