Skip to content

fix: stop serving stale app.js, harden service worker and download names (v1.5.2) - #15

Merged
NX1X merged 1 commit into
mainfrom
fix/asset-cache-1.5.2
Jul 25, 2026
Merged

fix: stop serving stale app.js, harden service worker and download names (v1.5.2)#15
NX1X merged 1 commit into
mainfrom
fix/asset-cache-1.5.2

Conversation

@NX1X

@NX1X NX1X commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Why

A pentest of convert.nx1xlab.dev found production serving a year-old app.js:

GET /app.js       -> 43,079 bytes, cf-cache-status: HIT, age: 19338
GET /app.js?cb=x  -> 58,463 bytes, sha256 identical to origin/main

The origin was current; the edge was not. So none of the 1.4.0 or 1.5.0 fixes (DoS hardening, stack-overflow fix, RTL rework, CSV CR quoting, filename handling) were reaching a single visitor. The stale copy also lacked detectColumnIntervals, mergeContainedFragments and the xlsx text-format guard.

Root cause: _headers gave /*.js and /*.css a one-year max-age, but app.js and style.css carry no content hash and index.html referenced them bare (<script src="app.js">). Nothing could ever bust that cache. The service worker then precached the stale copy and pinned it client-side.

What changed

Cache correctness

  • app.js and style.css now revalidate on every request (max-age=0, must-revalidate); ETag makes that a cheap 304. Icons and the social card get a one-day cache; the blanket one-year rule is gone
  • index.html references both with ?v=1.5.2, so a release busts any intermediary cache that ignores the header
  • scripts/update-version.js bumps that query and sw.js's APP_VERSION alongside package.json
  • New CI gate scripts/check-version-sync.sh fails the build on version drift or a missing cache-busting query

Service worker

  • App shell is network-first with a cache fallback for offline; it was cache-first, which pinned whatever build happened to be cached and (per the pentest) would also let a hypothetical one-shot script execution poison /app.js in Cache Storage beyond the patch that fixed it
  • Version-pinned CDN libraries stay cache-first (their URL changes with the version, and SRI re-validates the two script-tag libs regardless)
  • Cross-origin requests are no longer intercepted, so a failed Turnstile or analytics request can never be answered with an unrelated cached response
  • skipWaiting() + clients.claim() so a new worker takes over promptly

Download filenames

  • Bidi overrides, control characters and path separators are stripped and the name is length-capped. The real extension was never attacker-controlled (the app always appends _converted.xlsx/.csv), so this closes the cosmetic save-dialog spoofing angle only

Documentation

  • _headers now records the zone-level overrides observed in production (X-Frame-Options: SAMEORIGIN instead of DENY, Referrer-Policy: same-origin, Pages-added Access-Control-Allow-Origin: *, deprecated Expect-CT), so the file is not read as authoritative

Test plan

  • check-version-sync.sh passes at 1.5.2; negative-tested against a stale sw.js, a stale index.html query, and a removed query - all three fail correctly
  • check-cdn-versions.sh and check-action-pins.sh pass
  • node --check on app.js, sw.js, _worker.js
  • Served public/ locally: /, /app.js?v=1.5.2, /style.css?v=1.5.2, /sw.js, /manifest.json all 200, index references resolve to the current build
  • sanitizeFileName unit-checked: RTL override stripped, traversal neutralised, control chars removed, Hebrew names survive intact, empty input falls back to document
  • After merge: confirm curl -sSI https://convert.nx1xlab.dev/app.js shows the new cache-control and the response body matches origin/main

Not fixed here (needs the Cloudflare dashboard)

  • Purge the CDN cache once. Disabling the build cache does not touch the edge cache - the stale object is still being served (cf-cache-status: HIT). Caching > Configuration > Purge Everything
  • X-Frame-Options / Referrer-Policy overrides come from a zone-level Transform Rule or managed header, not from _headers
  • The served robots.txt has Cloudflare Managed Content injecting User-agent: * / Allow: / ahead of the repo's Disallow: /, which likely defeats the intent to stay unindexed
  • /.well-known/security.txt is injected at zone level (it is not in the repo and bypasses the _worker.js allowlist); its Canonical: points at nx1xlab.dev rather than the host serving it

Production was serving a year-old app.js: _headers gave the
non-fingerprinted app.js and style.css a one-year max-age, so the edge
kept a stale build while the origin was current, and no fix from 1.4.0
or 1.5.0 reached visitors. Both now revalidate every request and
index.html references them with a ?v=<version> query.

- sw.js: app shell is network-first with a cache fallback for offline
  (was cache-first, which pinned whatever build was cached and would
  also outlive a one-shot script execution); CDN libs stay cache-first
  since their URLs are version-pinned; cross-origin requests are no
  longer intercepted, so a failed Turnstile or analytics request cannot
  be answered from cache
- app.js: strip bidi overrides, control characters and path separators
  from the download name and cap its length
- scripts/check-version-sync.sh: new CI gate failing on version drift
  between package.json, sw.js and the index.html asset queries
- update-version.js keeps the asset query and sw.js APP_VERSION in step
- _headers documents the zone-level header overrides seen in production
@cloudflare-workers-and-pages

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

Copy link
Copy Markdown

Deploying easyconvert-website with  Cloudflare Pages  Cloudflare Pages

Latest commit: ce0712c
Status: ✅  Deploy successful!
Preview URL: https://a700168b.easyconvert-website.pages.dev
Branch Preview URL: https://fix-asset-cache-1-5-2.easyconvert-website.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@NX1X, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ed6d32e8-c527-4118-af21-a1b68ee55583

📥 Commits

Reviewing files that changed from the base of the PR and between 483aaf6 and ce0712c.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • CHANGELOG.md
  • README.md
  • package.json
  • public/_headers
  • public/app.js
  • public/index.html
  • public/sw.js
  • scripts/check-version-sync.sh
  • scripts/update-version.js

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.

@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@NX1X
NX1X merged commit ce08423 into main Jul 25, 2026
10 checks passed
@NX1X
NX1X deleted the fix/asset-cache-1.5.2 branch July 25, 2026 11:58
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.

1 participant