v154 · security · background fetch

CORS enforcement for Background Fetch

Background Fetch downloads survive the page that started them, so they run through the service worker rather than the document. In Chromium they were also skipping the security policies every other fetch goes through — which made the API a way around CORS rather than a way to download in the background. Chrome 154 closes it.

concepts

  1. A background fetch that works

    Register a real service worker and download a set of same-origin files, with progress and the per-record results reported from the worker. The baseline: if this does not work, nothing below means anything.

  2. With and without CORS headers

    The same download aimed at a genuinely cross-origin URL — 127.0.0.1 and localhost are different origins even on one server — with the CORS header switched on and off. The difference between the two runs is the enforcement.

  3. What the change means for your code

    The three ways an existing background fetch can start failing, what each looks like from the service worker, and what to do about it — checked against what this browser actually reports.

why it shipped

The Background Fetch specification has always said the requests go through Fetch, with the same policies applied. Chromium's implementation did not, so a site could reach a cross-origin resource in the background and read the bytes out of the cache — no Access-Control-Allow-Origin needed, no preflight, none of the checks that exist to stop a page reading things it was never given permission to.

That is a bypass, and the shape of it matters: it did not require a bug, just the API. Aligning the implementation with the spec means Background Fetch is subject to CORS, to Local Network Access, and to everything else in the fetch pipeline — which is what everyone already assumed was true.

the API

const registration = await navigator.serviceWorker.ready;
const fetch = await registration.backgroundFetch.fetch(
  "assets-v3",
  ["/a.bin", "https://cdn.example.com/b.bin"],
  { title: "Downloading assets", downloadTotal: 4_000_000 },
);

// In the service worker:
self.addEventListener("backgroundfetchsuccess", (event) => {
  event.waitUntil((async () => {
    for (const record of await event.registration.matchAll()) {
      const response = await record.responseReady;   // rejects if CORS blocked it
    }
  })());
});

references