Skip to content

public/sw.js fetch handler: caches.match() reads are not scoped to owned caches #514

Description

@qnbs

Context

DA-03 (#513) fixed every deletion site in public/sw.js to only ever delete a positively-owned CacheStorage entry — never a cache belonging to another app sharing the qnbs.github.io origin.

chatgpt-codex-connector flagged a related but structurally separate gap in the same file, out of DA-03's declared scope (public/sw.js deletion boundary only): the fetch handler's caches.match() calls are not scoped to a specific owned cache name, e.g.:

  • public/sw.js — JS/CSS Cache-First strategy: caches.match(request)
  • public/sw.js — navigation fallback: (await caches.match(request)) || (await caches.match(${BASE}index.html))

CacheStorage is origin-scoped, not path-scoped — so on a shared origin like qnbs.github.io (which hosts multiple independent GitHub Pages projects on different paths), a bare caches.match(request) searches every cache on the origin, not just this app's own CACHE_STATIC/CACHE_DYNAMIC/CACHE_IMAGES. In principle a different project's cached Response for a request whose full URL happens to coincide with one WorldScript Studio fetches could be served here.

Why this is deferred, not a release blocker

  • Practical exploitability is low: WorldScript Studio's own asset URLs are prefixed with /WorldScript-Studio/... and content-hashed by Vite; a collision requires another origin-sibling app to fetch/cache the exact same full URL (realistically only possible for identical externally-hosted resources, not same-origin paths).
  • Different mechanism than DA-03: DA-03 is about deleting another app's data (destructive, origin-shared-tenant blast radius). This is about reading a cached response — no data loss, at most a stale/wrong-but-harmless read in an already-narrow window.
  • Flagged as codex severity P2.

Suggested fix

Scope every caches.match() / cache.match() call in the fetch handler to an explicit owned cache name ({ cacheName: CACHE_STATIC } etc., or open the specific cache via caches.open(CACHE_X) first, matching the pattern already used elsewhere in the same handler for images/locales/dynamic content).

Scope

public/sw.js fetch handler only. Same "positively-proven ownership" invariant as DA-03, applied to reads instead of deletes.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions