ScreenshotNeo

BlogEngineering

Why Chromium Sends Sec-Fetch-Site: Cross-Site for Stylesheets

Chromium marks a stylesheet cross-site when its page and CSS URL are different sites. Learn site versus origin, redirects, headers, and safe policies.

By the ScreenshotNeo team1 October 20266 min read

Why Chromium Sends Sec-Fetch-Site: Cross-Site for Stylesheets

Short answer: Chromium sends Sec-Fetch-Site: cross-site when the page that initiates a request and the URL receiving it belong to different sites. A stylesheet does not get a special value. The browser classifies the relationship between the initiator and the target, so a CSS file hosted on another site is a cross-site subresource.

The header is request metadata, not a statement about whether CSS is safe or unsafe. Read it with the initiator, request URL, redirect chain, Sec-Fetch-Dest, and Sec-Fetch-Mode. The Fetch Metadata Working Draft defines the relationship values; Chromium’s network helper computes the value for browser requests.

What Sec-Fetch-Site means

Sec-Fetch-Site exposes the relationship between a request initiator’s origin and its target’s origin. Its values are:

The header describes the relationship between the page initiator and the stylesheet target.
The header describes the relationship between the page initiator and the stylesheet target.
Value Meaning Typical stylesheet example
same-origin Scheme, host, and port match. https://app.example/css/app.css loaded by https://app.example.
same-site The URLs are in the same site but can have different origins. https://cdn.example.com/app.css loaded by https://www.example.com.
cross-site The initiator and target are different sites. https://static.cdn.test/app.css loaded by https://shop.example.
none No initiator origin is available for a browser-initiated request. A request made without a page initiator, where the browser permits this value.

Origin comparison uses scheme, host, and port. Site comparison is broader, so two subdomains can be cross-origin while still being same-site. Scheme differences can also change the site relationship. Do not infer either result by comparing only the visible host text.

Why a stylesheet is marked cross-site

Consider this document:

<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="https://static.cdn.test/site.css">
  </head>
  <body>Shop</body>
</html>

The document is the request initiator. Chromium compares that page with https://static.cdn.test/site.css. Because they are different sites, the CSS request carries Sec-Fetch-Site: cross-site. The same request can carry Sec-Fetch-Dest: style; style identifies the destination, while cross-site identifies provenance. Sec-Fetch-Mode describes the request mode. These headers answer separate questions.

Inspect the request that produced the value

  1. Open DevTools and select Network.
  2. Reload the page with the network log preserved.
  3. Filter by CSS or select the stylesheet request.
  4. In Headers → Request Headers, record Sec-Fetch-Site, Sec-Fetch-Dest, Sec-Fetch-Mode, the Referer, and the full request URL.
  5. Check whether the request redirected. Inspect every URL in the redirect chain, not only the final URL.

A minimal server endpoint can print the metadata while you reproduce the request:

const http = require('node:http');

http.createServer((req, res) => {
  console.log({
    url: req.url,
    site: req.headers['sec-fetch-site'],
    dest: req.headers['sec-fetch-dest'],
    mode: req.headers['sec-fetch-mode'],
    referer: req.headers.referer
  });
  res.writeHead(200, {'content-type': 'text/plain'});
  res.end('logged');
}).listen(8080, () => console.log('Listening on http://localhost:8080'));

Point a test stylesheet at http://localhost:8080/style.css, load the page, and compare the logged values with DevTools. A command-line client such as curl is useful for checking your server response, but it does not reproduce Chromium’s browser-generated Fetch Metadata headers unless you add request headers yourself.

Redirects can preserve a cross-site classification

Chromium evaluates the request URL list, including redirects. For example, a page on shop.example may request cdn.example, which redirects through assets.other.test and eventually returns to cdn.example. The cross-site hop can leave the eventual request classified as cross-site. When diagnosing an unexpected value, capture the complete chain and inspect each host and scheme.

Redirects are part of the request URL list Chromium considers.
Redirects are part of the request URL list Chromium considers.

Writing a safe server policy

Fetch Metadata is a signal for resource isolation, not an authorization system. A blanket rule that rejects every cross-site request can break intentionally shared CSS, fonts, images, and APIs. Decide by resource and intended use, then keep endpoint-specific authorization and CORS where callers need to read response data. The MDN Fetch Metadata guide describes this deployment context.

Example policy for a public stylesheet

function allowStylesheet(req) {
  const site = req.headers['sec-fetch-site'];
  const dest = req.headers['sec-fetch-dest'];

  // This endpoint is deliberately public. Use authentication or an
  // allowlist for private resources instead of this example.
  if (dest === 'style' && (site === 'same-origin' || site === 'same-site' || site === 'cross-site')) {
    return true;
  }
  return false;
}

// In a real handler, return the CSS only after applying your own
// authentication, tenant checks, rate limits, and cache rules.

Example policy for a private endpoint

function fetchMetadataLooksSafe(req) {
  const site = req.headers['sec-fetch-site'];
  const mode = req.headers['sec-fetch-mode'];
  const dest = req.headers['sec-fetch-dest'];

  // Tune this to the endpoint. Do not treat it as proof of identity.
  return (site === 'same-origin' || site === 'same-site')
      && (mode === 'cors' || mode === 'same-origin')
      && dest !== 'object';
}

Log rejected requests during rollout and test browsers, service workers, proxies, redirects, and non-browser clients. Older clients may omit these headers, so choose an explicit fallback instead of assuming omission means cross-site.

Common causes and fixes

Symptom Cause Fix
CSS from a CDN is rejected. The CDN is a different site and your policy denies cross-site. Allow that public stylesheet endpoint, or serve it from an intended same-site host.
A subdomain is not same-origin. Hosts differ, even though they share a registrable domain. Use same-site rules only where appropriate; do not confuse them with origin checks.
The final URL looks same-site but remains cross-site. An earlier redirect crossed sites. Inspect and fix the redirect chain, or account for it in the policy.
Sec-Fetch-Dest is style but site is cross-site. The headers describe destination and provenance independently. Evaluate each field for its own purpose.
Headers are missing in a script or test. Fetch Metadata is browser-supplied metadata; non-browser clients may omit it. Use explicit authentication and a documented fallback. Add headers in a test only to simulate a browser request.
Changing Referer does not change the value. Sec-Fetch-Site is computed from initiator and target, not copied from the referrer string. Verify the actual initiator, target, scheme, and redirects.

Performance and reliability considerations

  • The header itself adds little processing; the expensive work is normally CSS transfer, parsing, redirects, and policy checks.
  • Keep shared CSS on a stable URL and use normal HTTP caching. Avoid unnecessary cross-site redirects because they complicate classification and add latency.
  • When enforcing a policy, measure blocked requests by destination and browser family before tightening it. Preserve an operational fallback for clients that do not send Fetch Metadata.
  • Do not use this header as the only fraud or access-control signal. It is useful context and can be absent, while authentication and authorization establish who may access a resource.

Capture a reproducible page while debugging

For a visual record of a page that loads cross-site stylesheets, capture the page after reproducing the request. A screenshot preserves the rendered result, while DevTools and server logs preserve the request metadata.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. See the API docs for all options. This one request captures a page as a WebP file:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Before capture, cookie and consent banners are accepted and 60+ known consent platforms, newsletter popups, and chat widgets are removed; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response reports the result in X-Page-Verdict and X-Billed headers. An MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Does cross-site mean the stylesheet is blocked?

No. It is a classification. Your server, CSP, CORS, or another policy may block the request, but the header alone does not.

Can I force Chromium to send same-site?

Only by changing the actual initiator or target relationship. Editing a request header in application JavaScript is not a supported way to override browser Fetch Metadata.

Is a CDN always cross-site?

No. A CDN hostname can be same-site in some deployments. Compute the relationship from scheme and site rules, then verify the request in DevTools.

Which header tells me that this is CSS?

Sec-Fetch-Dest: style describes the destination. Sec-Fetch-Site remains the site relationship.

Should every server reject cross-site?

No. Public resources often need cross-site access. Apply a policy that matches the endpoint’s intended use.