Skip to content

fix(web): harden service worker against SPA HTML cache poison - #2289

Merged
lefarcen merged 5 commits into
mainfrom
fix/sw-cache-mime-guards
Jul 28, 2026
Merged

fix(web): harden service worker against SPA HTML cache poison#2289
lefarcen merged 5 commits into
mainfrom
fix/sw-cache-mime-guards

Conversation

@mrcfps

@mrcfps mrcfps commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Bump SW cache schema to app-cache-v2 and drop only known legacy bucket app-cache-v1 on activate
  • Restrict CacheFirst to hashed /static/**/*.{js,css} with MIME validation on install, runtime read/write, and background precache
  • Reject 200 text/html SPA fallbacks so they can never be stored/served as scripts (fixes post-rollback MIME errors)

Context

After a bad Pages deploy + rollback, some clients cached SPA HTML under JS URLs via CacheFirst + statuses: [200]. Edge purge cannot heal Cache API poison. This change prevents recurrence and clears v1 for clients that can load the new SW.

Note: Fully broken clients (app never boots) still need a one-time site-data clear; new SW registration requires enough JS to load.

Test plan

  • Build web and confirm new service-worker.<hash>.js is emitted
  • Fresh profile: login page loads; SW installs; /static/js/*.js CacheFirst hits are real JS MIME
  • Simulate poison: put text/html under a .js cache key → next request drops entry and refetches JS
  • After activate, app-cache-v1 is deleted; unrelated caches untouched
  • /v1/* and /api/* are not CacheFirst-cached
  • Hard-refresh / version bump still notifies client as before for HTML shells

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened offline caching by validating static asset content types before storing them, and rejecting invalid responses.
    • Prevented “poisoned” or mismatched cached entries by cleaning invalid items during both caching and retrieval.
    • Updated precaching to only cache validated critical assets, failing fast if any are invalid.
    • Migrated legacy static caches to the newer cache format and tightened HTML shell caching on app version changes.
    • Kept /api/ and /v1/ requests network-only; routed static hashed JS/CSS through a dedicated /static/ CacheFirst strategy.

Bump cache schema to app-cache-v2 and only CacheFirst hashed /static
JS/CSS with MIME validation on install, runtime, and background
precache so 200 text/html SPA fallbacks cannot be stored as scripts.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d364220e-5163-4942-a092-4e5850b1eb4b

📥 Commits

Reviewing files that changed from the base of the PR and between 51d6ca8 and a8b2acb.

📒 Files selected for processing (1)
  • apps/web/src/service-worker-sw.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/service-worker-sw.ts

📝 Walkthrough

Walkthrough

The service worker now uses cache schema v2 with MIME- and path-aware validation. Install, activation, runtime routing, HTML invalidation, static asset caching, and background precaching reject invalid response types and handle legacy caches selectively.

Changes

Service worker cache schema v2

Layer / File(s) Summary
Cache validation and lifecycle
apps/web/src/service-worker-sw.ts
Adds schema v2, MIME validation helpers, validated writes, filtered install precaching, and targeted migration and cleanup of the legacy cache.
HTML and API routing
apps/web/src/service-worker-sw.ts
Routes /api/ and /v1/ through NetworkOnly, validates HTML writes, and narrows HTML-shell invalidation and proactive route precaching.
Static asset caching pipeline
apps/web/src/service-worker-sw.ts
Restricts CacheFirst handling to hashed /static/ JavaScript and CSS, heals invalid cached entries, and validates background precaching.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ServiceWorker
  participant CacheStorage
  participant Network
  Browser->>ServiceWorker: request hashed /static/ JS or CSS
  ServiceWorker->>CacheStorage: check cached response
  CacheStorage-->>ServiceWorker: return cached response
  ServiceWorker->>Network: fetch when missing or invalid
  Network-->>ServiceWorker: return static response
  ServiceWorker->>CacheStorage: store validated response
  ServiceWorker-->>Browser: return response
Loading

Possibly related PRs

  • refly-ai/refly#2101: Refactors the same service worker’s cache buckets and precaching behavior.
  • refly-ai/refly#2133: Adjusts HTML caching and static asset fallback behavior in the same service worker.
  • refly-ai/refly#2210: Updates version-change HTML invalidation and proactive route precaching.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: hardening the web service worker against SPA HTML cache poisoning.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sw-cache-mime-guards

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploying refly-branch-test with  Cloudflare Pages  Cloudflare Pages

Latest commit: a8b2acb
Status: ✅  Deploy successful!
Preview URL: https://4745834c.refly-branch-test.pages.dev
Branch Preview URL: https://fix-sw-cache-mime-guards.refly-branch-test.pages.dev

View logs

Fail install (no skipWaiting) when critical precache fails so v2
does not claim clients or delete v1 with an incomplete cache. Also
require HTML MIME and same-origin when clearing document shells.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bdbd10498

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/service-worker-sw.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/service-worker-sw.ts`:
- Around line 225-246: Bound each critical asset fetch in the install precache
flow by adding a per-request timeout with AbortController, and pass its signal
to fetch in the criticalUrls Promise.all map. Ensure the timeout aborts stalled
requests so installation fails promptly while preserving the existing fail-fast
validation and skipWaiting behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ab6d390-162b-4628-a6a1-754c66692591

📥 Commits

Reviewing files that changed from the base of the PR and between 7bdbd10 and c832bb6.

📒 Files selected for processing (1)
  • apps/web/src/service-worker-sw.ts

Comment thread apps/web/src/service-worker-sw.ts
- Fix TS2322: cachedResponse is optional in Workbox callback param
- On activate, copy MIME-valid /static JS/CSS from app-cache-v1 into
  v2 before deleting v1 so open tabs keep lazy chunks

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8904a8640c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/service-worker-sw.ts Outdated
Migrate v1 static assets sequentially, delete each source entry after
copy, and never let QuotaExceededError block activate/claim.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 51d6ca8555

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/service-worker-sw.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/web/src/service-worker-sw.ts (1)

286-298: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Unnecessary .clone()response isn't reused after this line.

response (from legacy.match(request)) is only ever passed to target.put(); nothing else in this function reads it afterward. Cloning it just to hand the clone to put() adds an extra body tee for no benefit, which is a bit ironic in a function whose whole purpose is to be quota/memory-conscious.

♻️ Proposed cleanup
       const existing = await target.match(request);
       if (!existing) {
-        await target.put(request, response.clone());
+        await target.put(request, response);
         migrated += 1;
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/service-worker-sw.ts` around lines 286 - 298, Remove the
unnecessary response.clone() call in the migration flow around legacy.match and
target.put; pass the response directly to target.put(request, response),
preserving the existing cache checks, migration count, and legacy deletion
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/web/src/service-worker-sw.ts`:
- Around line 286-298: Remove the unnecessary response.clone() call in the
migration flow around legacy.match and target.put; pass the response directly to
target.put(request, response), preserving the existing cache checks, migration
count, and legacy deletion behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b9bb45ff-8432-4899-9b14-ec1c5ed490b1

📥 Commits

Reviewing files that changed from the base of the PR and between 8904a86 and 51d6ca8.

📒 Files selected for processing (1)
  • apps/web/src/service-worker-sw.ts

Also carry MIME-valid non-home/non-SSR document entries into v2 so
offline refresh keeps working after legacy bucket delete. Drop
unnecessary response.clone() on migrate put.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a8b2acbd4b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/service-worker-sw.ts
@lefarcen
lefarcen merged commit 68f1aa6 into main Jul 28, 2026
5 checks passed
@mrcfps
mrcfps deleted the fix/sw-cache-mime-guards branch July 28, 2026 08:00
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.

2 participants