Skip to content

fix: answer the address a name actually has - #15

Merged
ralyodio merged 1 commit into
mainfrom
fix/address-answer
Aug 4, 2026
Merged

fix: answer the address a name actually has#15
ralyodio merged 1 commit into
mainfrom
fix/address-answer

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This bridge could not resolve most of the registry.

before   ;; flags: qr aa rd;  QUERY: 1, ANSWER: 0, AUTHORITY: 0
after    seo.rank.  30  IN  CNAME  dev.profullstack.com.

Both measured against the live registry, on seo.rank, with only this diff between them.

ANSWER: 0 with aa set is an authoritative "the name exists and has nothing for you", which a client is entitled to treat as final. In practice it surfaces as:

curl: (6) Could not resolve host: seo.rank

— for a name the registry holds perfectly well, on a machine whose DNS is otherwise fine. dig confirms the name exists, nothing can reach it, and no log anywhere reports an error.

Two causes, both on the address path

A published A/AAAA record was never read. RECORD_TYPES covered CNAME, MX and TXT; addresses came only from target. A name publishing an AAAA in the registry answered nothing to an AAAA query.

A hostname target produced nothing. targetAddress() returns null for a host and nothing picked up after it. Most of the registry points at a host, so this was the common case, not an edge one.

The change

addressAnswer() replaces the answerPolicy-then-look-for-a-CNAME pair with a single plan: target address → published A/AAAA → published CNAME → a CNAME synthesised from a hostname target → NODATA.

The cheap question is still asked first. A name pointed at a bare IP costs one registry call and no record fetch — the fast path every page load takes, and there is a test pinning that it stays that way.

targetHostname() is the other half of targetAddress(), refusing anything a CNAME cannot carry: ports, paths, bare IPs, single labels. A target naming :8080 stays NODATA rather than quietly sending the client to port 80 of the right host, which would be a wrong answer that looks right.

Why the chain is left dangling here

buildChainResponse() can attach leaf addresses, and the tests cover that, but the server does not pass any.

This bridge is routed per-TLD (~rank, ~hacker), so the CNAME's target is not a name that comes back to it — the machine's own resolver, a full recursive one, chases it. Resolving it here would mean this bridge doing clearnet DNS to answer a question the system already answers, which is precisely what it deliberately does not do.

That is the one place this port differs from moshcode#269. That copy sits behind Domains=~., where the target does come back to it and a dangling CNAME would strand the client, so it completes the chain from its upstreams.

Tests

14 new in test/dns-address-answer.test.mjs, reading answers back off the wire — a reply of the right shape with the wrong bytes is exactly the failure being fixed. They cover both owner names in a chain, a leaf of the wrong family being dropped rather than encoded as garbage, the fast path not fetching records, and NXDOMAIN surviving all of it.

Full suite: 102 pass, 0 fail.

The surface shared with moshcode's copy is unchanged, so the drift test pinning them together still holds.

This bridge could not resolve most of the registry. Every name pointed at
a host came back as an authoritative NOERROR with no answers, which a
client is entitled to treat as final — in practice, `curl: (6) Could not
resolve host` for a name the registry holds perfectly well.

    before   ;; flags: qr aa rd;  ANSWER: 0
    after    seo.rank. 30 IN CNAME dev.profullstack.com.

Two independent causes, both on the address path:

  - RECORD_TYPES covered CNAME, MX and TXT only, so a published A or AAAA
    record was never consulted for an address question. A name publishing
    an AAAA in the registry still answered nothing.

  - targetAddress() returns null for a hostname and nothing picked up
    after it. Most of the registry points at a host, so this was the
    common case rather than an edge one.

addressAnswer() replaces the answerPolicy-then-look-for-a-CNAME pair with
one plan: target address, published A/AAAA, published CNAME, then a CNAME
synthesised from a hostname target. The cheap question is still asked
first, so a name pointed at a bare IP costs one registry call and no
record fetch — the fast path every page load takes.

The chain is deliberately left dangling. Unlike a catch-all bridge, this
one is routed per-TLD, so the CNAME's target is not a name that comes
back here: the machine's own resolver — a full recursive one — chases it.
Completing it here would mean this bridge doing clearnet DNS to answer a
question the system already answers, which is the thing it does not do.

Ported from moshcode#269, which fixed the same two gaps in the copy that
does forward. The shared surface both sides pin stays unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

0 finding(s)

No findings.

@ralyodio
ralyodio marked this pull request as ready for review August 4, 2026 01:09
@ralyodio
ralyodio merged commit cb9dbf9 into main Aug 4, 2026
2 checks passed
ralyodio added a commit that referenced this pull request Aug 4, 2026
Ships the resolver fix from #15, which has been on main since it merged
but was never published: the version stayed at 0.4.0, so
`npm i @moshcoder/moshpit-dns` still gives you a bridge that answers
`ANSWER: 0` for names that resolve perfectly well.

That authoritative-empty answer is the worst shape available — a client
treats it as final, `dig` reports the name exists, and nothing logs an
error. The bug is invisible from the outside, which is why it survived a
release.

Same pattern as #14: a fix sitting behind a stale version is not shipped,
it is just merged.
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