Skip to content

feat(dns): send a name nobody holds to the pit instead of an error page - #79

Merged
ralyodio merged 1 commit into
masterfrom
feat/moshpit-catchall
Jul 31, 2026
Merged

feat(dns): send a name nobody holds to the pit instead of an error page#79
ralyodio merged 1 commit into
masterfrom
feat/moshpit-catchall

Conversation

@ralyodio

Copy link
Copy Markdown
Collaborator

Yes — mosh.whatever should land on the pit ready to register. This is the resolver + gateway half; moshcoder/moshcode#124 is the page it lands on.

The flow

mosh.whatever  →  resolver: nobody holds it, and `.whatever` is not in the legacy root
               →  the gateway's address
               →  302 https://app.moshcode.sh/pit?name=mosh.whatever
               →  "Nobody holds .whatever." + claim form, prefilled

Two halves

The gateway redirect (deploy/Caddyfile.gateway) — any Moshpit Host it does not serve now goes to app.moshcode.sh/pit?name=<host>. A redirect rather than a proxy on purpose: proxying would leave people signing in under a hostname no CA will vouch for, with a session cookie on a domain the app does not own. The redirect puts them on the real origin with a real padlock, where signing in works.

MOSHPIT_DNS_CATCHALL (off by default) — without it, unclaimed names never resolve at all, so the funnel only works for endings someone already claimed, which is the wrong half of the audience.

The boundary is the whole feature

asdkjh.com is NXDOMAIN too. Answering that would make this resolver a typo-squatter for the entire internet — what ISPs were rightly hated for. So the catch-all fires only when the TLD itself is absent from the legacy root, checked by asking the upstreams for its SOA (one cached query per ending, not per name) instead of shipping an IANA list that is stale the week after it is written.

An unreachable upstream fails closed — unknown means "the root has it" — because the alternative is handing the whole internet to the catch-all during an outage.

Answers say what they are: aa is 0 (nobody holds the name; claiming authority would be a lie) and TXT reports unclaimed=1.

Verified

8 new tests covering exactly the ways this could go wrong:

  • unclaimed name under a non-root ending → gateway, aa=0, counted as catchall
  • asdkjh.com → NXDOMAIN, untouched
  • catch-all off → NXDOMAIN
  • a claimed name still answers authoritatively as itself
  • clearnet still wins whenever clearnet answers
  • the root is asked once per ending, not per name
  • an unreachable root fails closed

tsc --noEmit clean. Suite: 123 pass, 1 fail — tests/coinpay-amount.test.mjs, which fails on master independently of this branch (git log blames #59; setupAmountUsd compares float error against Number.EPSILON, so 19.99 throws). Not touched here.

Off by default

Turning it on makes the resolver answer for names the registry never granted. That is a product decision, so it is a flag, not a default — flip MOSHPIT_DNS_CATCHALL=1 when you want the funnel.

🤖 Generated with Claude Code

Someone types `mosh.whatever`. Today the registry has no ending called
`.whatever`, so the resolver hands back clearnet's verdict — NXDOMAIN, a
browser error page, and a person who just demonstrated demand for a name walks
away. This turns that into the front door.

Two halves. The gateway now redirects any Moshpit Host it does not serve to
`app.moshcode.sh/pit?name=<host>`, where the pit offers them the ending the
name sits under (moshcoder/moshcode#124). A redirect rather than a proxy:
proxying would leave people signing in under a hostname no CA will vouch for,
with a session cookie on a domain the app does not own.

The other half is MOSHPIT_DNS_CATCHALL, off by default, which makes unclaimed
names resolve to the gateway at all. Without it the funnel only works for
endings someone already claimed, which is the wrong half of the audience.

The boundary is the entire feature. `asdkjh.com` is NXDOMAIN too, and answering
it would make this a typo-squatter for the whole internet — the behaviour ISPs
were rightly hated for. So the catch-all fires only when the TLD itself is
absent from the legacy root, checked by asking the upstreams for its SOA (one
cached query per ending, not per name) rather than by shipping an IANA list
that is stale the week after it is written. An unreachable upstream fails
closed: unknown means the root has it, because the alternative is handing the
internet to the catch-all during an outage.

Answers say what they are: `aa` is 0 since nobody holds the name, and TXT
reports `unclaimed=1`. Off by default — answering for names the registry never
granted is a product decision, not a default a resolver should assume.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio marked this pull request as ready for review July 31, 2026 04:45
@ralyodio
ralyodio merged commit 3a2a2c0 into master Jul 31, 2026
1 of 2 checks passed
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