Skip to content

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

Merged
MichaelTaylor3d merged 2 commits into
mainfrom
fix/asset-content-type-144
Jul 6, 2026
Merged

fix: 404 asset-looking paths instead of the text/html loader shell#4
MichaelTaylor3d merged 2 commits into
mainfrom
fix/asset-content-type-144

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

TLDR

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: The script has an unsupported MIME type ('text/html'). The resolver now serves a clean 404 for asset-looking paths instead of the text/html loader shell. Fixes DIG-Network/dig_ecosystem#144.

Root cause (SPA-fallback, not the MIME map)

Both candidates were checked; it is the SPA-fallback, at the Lambda:

  • A browser's service-worker registration fetch (and ES-module import) BYPASSES the page's controlling loader SW and hits the resolver origin directly.
  • The Lambda catch-all (src/bin/bootstrap.rs) served the branded loader shell (text/html) for every non-/__dig* path, so /service-worker.js came back as an HTML page → wrong MIME → registration rejected.
  • The loader SW's own contentType() map (assets/sw.js:171) already serves .js/.mjs as text/javascript for the subresources it serves — so the extension→MIME map was not the bug. The defect was purely the Lambda masquerading an asset path as the HTML shell.

Fix

  • is_static_asset_path() (src/lib.rs) classifies a request whose FINAL path segment carries a known non-HTML file extension (js, mjs, css, json, wasm, svg, fonts, images, …) as a static asset.
  • The resolver answers such a path with an honest 404 (text/plain, nosniff, no-store) — never the loader shell. The SPA-fallback shell stays only for navigation/HTML routes (no extension, or .html/.htm), so an SPA deep-link still boots the loader and a route that merely contains a dot (/user/john.doe) stays a navigation route.
  • A 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.

Site service workers are structurally unsupported (documented)

The resolver is a blind host — it 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 cannot function on *.on.dig.net. The clean 404 replaces the misleading SecurityError. Recorded in SPEC.md §3.1.

How verified

  • cargo fmt --all --check clean.
  • cargo clippy --all-targets -- -D warnings clean; cargo clippy --bin bootstrap --features aws -- -D warnings clean.
  • cargo test --lib — 44 pass, incl. new is_static_asset_path_classifies_assets_vs_navigation (service-worker.js/sw.js/.mjs/css/json/wasm/images → asset; /, /about, /index.html, /user/john.doe → navigation).
  • node --test test/sw.test.mjs — 20 pass, incl. the contentType() regression guard (service-worker.js / sw.js / .mjs → JS MIME, never text/html).

Regression coverage (issue acceptance)

  • .js asset → JS MIME (SW path) / clean 404 at the blind Lambda (never text/html).
  • /service-worker.js → JS MIME in the SW map; 404 (not text/html shell) at the resolver.
  • missing asset → 404, not the shell.

Security posture

Unchanged. No WAF, verify-then-decrypt fail-closed, LOADER_CSP/STORE_CSP and frame-ancestors all untouched. The 404 adds no new surface (it strictly narrows what the catch-all serves).

Blast radius

Additive: one new pure fn (is_static_asset_path, no prior callers) + one early-return branch in handle before the catch-all. Only asset-looking paths that previously fell through to the shell change behavior; /__dig* assets, config.json, unresolvable-host error page, and navigation routes are untouched.

Version bump

0.3.00.3.1 (patch, fix: — backwards-compatible, no public-API/behaviour change beyond the corrected asset response).

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

Root cause is the resolver's SPA-fallback, NOT a wrong 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
Lambda's catch-all served the branded loader shell (`text/html`) for EVERY
non-`/__dig*` 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 a JS MIME for the subresources it does serve; the defect was purely the
Lambda masquerading an asset path as the HTML shell.

Fix: the resolver 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. A 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 is a blind host and 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.

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 / .mjs resolve to a JS MIME, never text/html.

Refs DIG-Network/dig_ecosystem#144
@MichaelTaylor3d MichaelTaylor3d merged commit 7166caa into main Jul 6, 2026
10 checks passed
@MichaelTaylor3d MichaelTaylor3d deleted the fix/asset-content-type-144 branch July 6, 2026 19:21
MichaelTaylor3d added a commit that referenced this pull request Jul 10, 2026
* fix: 404 asset-looking paths instead of the text/html loader shell

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

Root cause is the resolver's SPA-fallback, NOT a wrong 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
Lambda's catch-all served the branded loader shell (`text/html`) for EVERY
non-`/__dig*` 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 a JS MIME for the subresources it does serve; the defect was purely the
Lambda masquerading an asset path as the HTML shell.

Fix: the resolver 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. A 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 is a blind host and 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.

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 / .mjs resolve to a JS MIME, never text/html.

Refs DIG-Network/dig_ecosystem#144

* chore(deps): bump num-bigint 0.4.7 -> 0.4.8 (0.4.7 yanked upstream)

Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d added a commit that referenced this pull request Jul 10, 2026
* fix: 404 asset-looking paths instead of the text/html loader shell

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

Root cause is the resolver's SPA-fallback, NOT a wrong 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
Lambda's catch-all served the branded loader shell (`text/html`) for EVERY
non-`/__dig*` 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 a JS MIME for the subresources it does serve; the defect was purely the
Lambda masquerading an asset path as the HTML shell.

Fix: the resolver 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. A 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 is a blind host and 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.

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 / .mjs resolve to a JS MIME, never text/html.

Refs DIG-Network/dig_ecosystem#144

* chore(deps): bump num-bigint 0.4.7 -> 0.4.8 (0.4.7 yanked upstream)

Co-Authored-By: Claude <noreply@anthropic.com>
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