Skip to content

fix: 404 asset-looking paths instead of the text/html loader shell#3

Closed
MichaelTaylor3d wants to merge 1 commit into
mainfrom
fix/asset-path-mime-not-loader-shell
Closed

fix: 404 asset-looking paths instead of the text/html loader shell#3
MichaelTaylor3d wants to merge 1 commit into
mainfrom
fix/asset-path-mime-not-loader-shell

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

What / why

A site served via *.on.dig.net that ships its own service-worker.js (live case: chatfish.on.dig.net, a dotnet SPA) could not register it — the browser rejected it with:

SecurityError: Failed to register a ServiceWorker … The script has an unsupported MIME type ('text/html').

Root cause (SPA-fallback, not the MIME map). A browser's service-worker registration fetch (and ES-module imports) bypass the page's controlling loader service worker and hit the resolver origin directly. The resolver's catch-all served the branded loader shell (text/html) for every path, so /service-worker.js came back as an HTML page → wrong MIME. The loader SW's own contentType() map already serves .js/.mjs as text/javascript; the defect was purely the Lambda masquerading an asset path as the HTML shell. (Fixes DIG-Network/dig_ecosystem#144.)

Fix

The resolver now classifies a request whose final path segment carries a known non-HTML file extension (js mjs css json wasm map svg png … woff2 …) as a static asset and answers it with an honest 404 (text/plain, nosniff, no-store) — never the loader shell. The SPA-fallback shell stays for navigation/HTML routes only (no extension, or .html/.htm), so:

  • an SPA client-side deep-link still boots the loader;
  • a route that merely contains a dot (/user/john.doe) stays a navigation route (only KNOWN asset extensions match);
  • /service-worker.js gets a clean 404 that fails registration without evicting the loader SW (a browser installs a SW only from a 2xx script) — so store content keeps decrypting, and the misleading SecurityError is gone.

The resolver cannot decrypt an encrypted store asset (and the loader SW already owns scope / — it is the content-decrypting mechanism), so a site-provided service worker is structurally unsupported on *.on.dig.net; a clean 404 is the honest answer. Assets the loader SW does serve (in-store subresources on a controlled page) keep their correct extension-derived Content-Type.

Ordering: the /__dig/*, /__dig_sw.js, and /__dig/config.json internal routes are matched before the asset-404 branch, so config.json (.json) and the baked JS/WASM assets are unaffected.

Tests (regression, TDD)

  • is_static_asset_path (Rust lib): service-worker.js / sw.js / firebase-messaging-sw.js / .mjs / .css / .json / .wasm / images → assets (→ 404); /, /about, /index.html, dotted SPA routes (/user/john.doe, /v1.2) → navigation (→ shell). 44 lib tests pass.
  • sw.js contentType() (Node): service-worker.js / sw.js / app.min.mjs resolve to a JS MIME, never text/html. 20 JS tests pass.

Verification

cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo clippy --bin bootstrap --features aws -- -D warnings, cargo test --all-targets (44 pass), node --test test/sw.test.mjs (20 pass) — all green locally.

Blast radius

is_static_asset_path is a new pure function; the only caller is the resolver handle() catch-all in bootstrap.rs, inserted after the internal /__dig/* + config.json routes. No cross-repo contract changes: the shared contentType() map (byte-identical with hub embed-core.ts / dig-embed.js) is unchanged; SYSTEM.md's exposed on.dig.net asset routes are unchanged; docs.dig.net documents only the on.dig.net handle model, not asset MIME serving.

Security posture

Unchanged. NO WAF, verify-then-decrypt fail-closed, and the loader/static-page CSPs are untouched. The change only converts a mislabeled text/html asset response into an honest, nosniff 404 — it never weakens content isolation.

Version

0.3.00.3.1 (patch — a backwards-compatible bug fix, no API/behaviour change for navigation routes).

A site served via *.on.dig.net that ships its own service-worker.js (e.g.
chatfish.on.dig.net) could not register it: the browser rejected it with
`SecurityError: The script has an unsupported MIME type ('text/html')`.

Root cause: a browser's service-worker REGISTRATION fetch (and ES-module
imports) BYPASS the page's controlling loader service worker and hit the
resolver origin directly. The resolver's catch-all served the branded loader
shell (`text/html`) for EVERY path, so `/service-worker.js` came back as an
HTML page — the wrong MIME. The loader SW's own `contentType()` map already
serves `.js`/`.mjs` as `text/javascript`; the defect was purely the Lambda's
SPA-fallback masquerading an asset path as the HTML shell.

Fix: the resolver now classifies a request whose final path segment carries a
known non-HTML file extension as a static asset and answers it with an honest
404 (`text/plain`, `nosniff`, `no-store`) — never the loader shell. The
SPA-fallback shell is served only for navigation/HTML routes (no extension, or
`.html`/`.htm`), so an SPA client-side deep-link still boots the loader, while a
route that merely contains a dot (`/user/john.doe`) stays a navigation route.
The 404 fails SW registration cleanly WITHOUT evicting the loader SW (a browser
installs a SW only from a 2xx script), so store content keeps decrypting.

The resolver cannot decrypt an encrypted store asset, so it cannot serve a
site's real service-worker.js bytes; and the loader SW already owns scope `/`
(it IS the content-decrypting mechanism), so a site-provided service worker is
structurally unsupported on *.on.dig.net. A clean 404 replaces the misleading
SecurityError. Assets the loader SW does serve (in-store subresources on a
controlled page) keep their correct extension-derived Content-Type.

Ordering: the `/__dig/*`, `/__dig_sw.js`, and `/__dig/config.json` internal
routes are matched before the asset-404 branch, so config.json (`.json`) and
the baked JS/WASM assets are unaffected.

Tests: `is_static_asset_path` classifies service-worker.js / sw.js / .mjs /
css / json / wasm / images as assets and navigation routes (incl. dotted SPA
routes) as non-assets; the sw.js `contentType()` regression asserts
service-worker.js / sw.js / app.min.mjs resolve to a JS MIME, never text/html.

Refs DIG-Network/dig_ecosystem#144
@MichaelTaylor3d MichaelTaylor3d deleted the fix/asset-path-mime-not-loader-shell branch July 6, 2026 19:09
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