fix(moshpit): decide the namespace by the ending, not by a DNS failure - #70
Merged
Conversation
Moshpit resolution inferred "clearnet has no answer for this name" from
ERR_NAME_NOT_RESOLVED. That inference only holds on a resolver that
reports failure honestly, and many do not: an NXDOMAIN-hijacking
resolver answers every nonexistent name with a wildcard host. On such a
connection blue.eggs "resolves", the error never fires, and in the
default mode nothing ever ran — every Moshpit name landed on the
hijacker's page. It was unfixable from inside the browser for as long
as DNS was the signal.
The ending is a better signal and we hold it locally: clearnet can only
answer for an ending that exists on the real internet. So a hostname now
falls into exactly one territory, decided from the hostname alone:
- moshpit — an ending IANA does not delegate. Resolved in BOTH modes,
told clearnetResolves=false, and never waiting on a DNS
error that may never come. This is the case that broke.
- clearnet — a real ending. The default mode returns before any storage
read or registry call, so ordinary browsing costs one Set
lookup. Only the opt-in 'moshpit' mode still consults the
registry, which is what that mode is for.
- reserved — .onion, .local, .test, .internal and friends: dropped in
both modes, never sent to the registry.
- none — not a Moshpit-shaped hostname.
The reserved territory closes a leak rather than saving a request. A v3
onion address is 56 alphanumeric characters plus .onion — exactly two
alphanumeric labels, so parseRegistryName accepted it and every Tor
navigation sent the onion address to the registry. The pit's hosts
deliberately bypass the SOCKS proxy, so that lookup left over clearnet
carrying the address being visited.
This makes the code match what the options page has always claimed:
"Moshpit names always resolve here — clearnet has never heard of those
endings. This setting only decides what happens when both namespaces
answer."
The shared resolution policy (decideResolution and friends) is
untouched, so all three copies still agree and moshpit-drift.test.js
stays green. The routing decision moved out of background.js into
moshpit-routing.js so it can be tested without standing up a service
worker.
scripts/update-tlds.mjs regenerates tld-data.js from IANA (currently
1438 endings, version 2026080300). It is a manual chore, not a build
step: a release should not depend on data.iana.org being up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
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.
Moshpit names never resolved on my connection, and the reason is not in the Moshpit code — it is the assumption underneath it.
The bug
Resolution inferred "clearnet has no answer for this name" from
ERR_NAME_NOT_RESOLVED. That only holds on a resolver that reports failure honestly. Mine does not:NXDOMAIN hijacking. Every nonexistent name answers, so
ERR_NAME_NOT_RESOLVEDnever fires, so in the default mode (where that error is the only trigger) nothing ever ran and every Moshpit name landed on the hijacker's page. No amount of fixing the resolution policy helps while DNS is the signal.The fix
Decide from the ending, which we hold locally — clearnet can only answer for an ending that exists on the real internet. Every hostname now falls into exactly one territory, decided without touching the network:
moshpitblue.eggsclearnetResolves=false, no waiting on a DNS errorclearnetgoogle.commoshpitmode looks upreserved*.onion,.localnonea.b.c,1.2.3.4This makes the code match what the options page has always claimed: "Moshpit names always resolve here — clearnet has never heard of those endings. This setting only decides what happens when both namespaces answer."
A leak this closes on the way
A v3 onion address is 56 alphanumeric characters plus
.onion— exactly two alphanumeric labels, soparseRegistryNameaccepted it:In
moshpitmode every Tor navigation therefore sent the onion address to the registry — and the pit's hosts deliberately bypass the SOCKS proxy, so that request left over clearnet carrying the address being visited. That is a deanonymization leak, not a wasted round-trip. Reserved endings are now dropped before the registry in either mode.Also relevant to the "Moshpit is slowing down browsing" report
In the default mode an ordinary navigation to a real ending now costs one
Setlookup — nochrome.storageread, no registry request. Previously every two-label hostname read storage, and inmoshpitmode every one of them cost a registry round-trip (8s budget) before the tab settled.Shape of the change
tlds.js+tld-data.js— IANA's 1,438 endings plus the reserved set, with the reasoning for each reserved entry.moshpit-routing.js— the territory decision, lifted out ofbackground.jsso it is testable without standing up a service worker.background.js— two listeners, now thin wiring.scripts/update-tlds.mjs— regenerates the list from IANA. Deliberately a manual chore, not a build step: a release should not depend ondata.iana.orgbeing up.The shared resolution policy (
decideResolutionand friends) is untouched, so all three copies still agree andmoshpit-drift.test.jsstays green.Verification
tlds.test.js14,moshpit-routing.test.js11) covering the hijack case, the.onionleak, the no-registry-traffic guarantee, and totality.pnpm build && pnpm test— full workspace green;apps/desktopgoes 8 → 10 test files, 68 extension tests pass including the untouched drift test.pnpm lint— exit 0.build-release.shcopies the extension wholesale then deletes*.test.js, so the three new runtime modules ship and the two test files do not.🤖 Generated with Claude Code