fix: 404 asset-looking paths instead of the text/html loader shell#4
Merged
Conversation
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
A site served via
*.on.dig.netthat ships its ownservice-worker.js(live case: chatfish.on.dig.net, a dotnet SPA) could not register it — the browser rejected it withSecurityError: The script has an unsupported MIME type ('text/html'). The resolver now serves a clean 404 for asset-looking paths instead of thetext/htmlloader 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:
import) BYPASSES the page's controlling loader SW and hits the resolver origin directly.src/bin/bootstrap.rs) served the branded loader shell (text/html) for every non-/__dig*path, so/service-worker.jscame back as an HTML page → wrong MIME → registration rejected.contentType()map (assets/sw.js:171) already serves.js/.mjsastext/javascriptfor 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.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.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.jsbytes; 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 inSPEC.md§3.1.How verified
cargo fmt --all --checkclean.cargo clippy --all-targets -- -D warningsclean;cargo clippy --bin bootstrap --features aws -- -D warningsclean.cargo test --lib— 44 pass, incl. newis_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. thecontentType()regression guard (service-worker.js / sw.js / .mjs → JS MIME, never text/html).Regression coverage (issue acceptance)
.jsasset → 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.Security posture
Unchanged. No WAF, verify-then-decrypt fail-closed,
LOADER_CSP/STORE_CSPand 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 inhandlebefore 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.0→0.3.1(patch,fix:— backwards-compatible, no public-API/behaviour change beyond the corrected asset response).