Skip to content

Commit 5f77ab8

Browse files
ralyodioclaude
andauthored
fix(dns): resolve names under a numeric ending (#100)
The registry sells `.2600`. The resolver refused to resolve anything under it: alt.2600 28 forwarded -> NXDOMAIN from upstream seo.rank 28 moshpit(backfill) -> answered `moshpitCandidate` rejected every all-numeric ending outright, so a registered name was never even looked up. Buying an ending that cannot resolve is the worst possible failure: the registry takes the money and the name is dead, with nothing in any log to say why. The rule was protecting against address literals — `1.2.3.4` must never be read as `3.4` in this namespace, or whoever registered `.4` could intercept traffic meant for a machine. But the dotted-quad guard above it already covers that, and the blanket version caught a much larger class than it needed to. Narrowed to what is actually address-shaped: a name whose *every* label is numeric. `10.0.0.1` and `192.168` stay rejected; `alt.2600` resolves. The registry still decides whether a name exists — this is only the shape filter, and it should exclude what can never be a name rather than what merely looks unusual. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 3677b99 commit 5f77ab8

2 files changed

Lines changed: 34 additions & 2 deletions

File tree

lib/dns/policy.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,17 @@ export function moshpitCandidate(qname: string): { name: string; label: string;
6262
const tld = parts[parts.length - 1];
6363
const label = parts[parts.length - 2];
6464
if (NEVER_MOSHPIT.has(tld)) return null;
65-
if (/^\d+$/.test(tld)) return null;
65+
// A name whose every label is a number is address-shaped, and reading
66+
// `10.0.0.1` or `192.168` as a name in this namespace would let anyone who
67+
// registered `.1` intercept traffic meant for a machine. A *numeric ending*
68+
// under a real label is a different thing: `.2600` is a registered ending,
69+
// and `alt.2600` can only ever be a name. Rejecting the whole class here
70+
// meant the registry sold endings the resolver then refused to resolve.
71+
//
72+
// The registry still decides whether the name exists. This is only the shape
73+
// filter, and it should exclude what can never be a name rather than what
74+
// merely looks unusual.
75+
if (parts.every((part) => /^\d+$/.test(part))) return null;
6676
if (tld.length < 2) return null;
6777
if (!LABEL.test(tld) || !LABEL.test(label)) return null;
6878
// Underscore-prefixed service labels (`_dmarc`, `_acme-challenge`) are legal

tests/dns-policy.test.mjs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,33 @@ test("names the legacy root already owns are never sent to the registry", () =>
2626
});
2727

2828
test("things that only look like names are not names", () => {
29-
for (const name of ["", "localhost", "203.0.113.7", "eggs", "scrambled.e", "-bad.eggs", "scrambled.123"]) {
29+
for (const name of ["", "localhost", "203.0.113.7", "eggs", "scrambled.e", "-bad.eggs"]) {
3030
assert.equal(moshpitCandidate(name), null, `${name} should not be a Moshpit candidate`);
3131
}
3232
});
3333

34+
test("a numeric ending is a real ending", () => {
35+
// `.2600` is registered, and the resolver used to refuse every name under
36+
// it — the registry sold endings that could not resolve. A numeric ending
37+
// under a real label can only ever be a name.
38+
const candidate = moshpitCandidate("alt.2600");
39+
assert.equal(candidate?.name, "alt.2600");
40+
assert.equal(candidate?.tld, "2600");
41+
assert.equal(candidate?.label, "alt");
42+
assert.equal(moshpitCandidate("www.alt.2600")?.name, "alt.2600", "subdomains of it still work");
43+
});
44+
45+
test("an address is still never read as a name", () => {
46+
// This is what the old blanket rule was protecting, and it has to keep
47+
// holding: if `10.0.0.1` were a candidate, whoever registered `.1` could
48+
// intercept traffic meant for a machine.
49+
for (const address of ["10.0.0.1", "203.0.113.7", "192.168", "1.2.3.4", "8.8.8.8", "12.34"]) {
50+
assert.equal(moshpitCandidate(address), null, `${address} is an address, not a name`);
51+
}
52+
// Colons are IPv6, rejected earlier and for the same reason.
53+
assert.equal(moshpitCandidate("2604:a880:400:d1:0:4:c3fe:1"), null);
54+
});
55+
3456
test("clearnet mode forwards first and lets the registry backfill", () => {
3557
const plan = planQuery({ question: question("scrambled.eggs"), rd: true, mode: "clearnet" });
3658
assert.equal(plan.action, "forward-first");

0 commit comments

Comments
 (0)