Skip to content

fix(dns): answer the address a name actually has - #268

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

fix(dns): answer the address a name actually has#268
ralyodio merged 1 commit into
mainfrom
fix/resolver-address-answer

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Most of the registry could not be resolved by our own resolver. Every name pointed at a host — seo.rank, chovy.hacker, alt.2600, all of them — came back as an authoritative NOERROR with no answers, which a client is entitled to treat as final.

That is the worst shape a failure can take. dig said the name existed, nothing could reach it, and no log anywhere reported an error. google.com resolved the whole time, so the machine looked healthy.

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. alt.2600 publishes an AAAA in the registry and still answered nothing.

  • targetAddress() returns null for a hostname, and nothing picked up after it. The comment said turning a host into an address would mean the bridge doing clearnet DNS — but it already forwards clearnet queries upstream, so that reasoning had gone stale.

addressAnswer() replaces the old 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 exactly one registry call and no record fetch — the fast path every page load takes.

A bare CNAME would not have been enough. This bridge sets RA=0, so a stub handed a dangling CNAME has been told in the same breath that nobody will chase it; systemd-resolved reports that as a name with no address. buildChainResponse() emits the leaf alongside it, best-effort, so a slow upstream costs the extra record and never the answer.

DoH gets the same treatment. Its own comment said a name that resolves over the bridge and not over DoH is the failure that endpoint exists to remove, and it carried the identical gap.

Verified against the live registry before any test was written:

seo.rank.             30 IN CNAME dev.profullstack.com.
dev.profullstack.com. 30 IN A     67.205.189.229
alt.2600.            300 IN AAAA  2604:a880:400:d1:0:4:c3fe:1

One existing test changed rather than added to: it had recorded "a live name pointed at a hostname is NODATA" as correct. It was the bug.

Most of the registry could not be resolved by our own resolver. Every
name pointed at a host — seo.rank, chovy.hacker, alt.2600, all of them —
came back as an authoritative NOERROR with no answers, which a client is
entitled to treat as final.

That is the worst shape a failure can take. `dig` said the name existed,
nothing could reach it, and no log anywhere reported an error. google.com
resolved the whole time, so the machine looked healthy.

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. alt.2600 publishes
    an AAAA in the registry and still answered nothing.

  - targetAddress() returns null for a hostname, and nothing picked up
    after it. The comment said turning a host into an address would mean
    the bridge doing clearnet DNS — but it already forwards clearnet
    queries upstream, so that reasoning had gone stale.

addressAnswer() replaces the old 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 exactly one registry
call and no record fetch — the fast path every page load takes.

A bare CNAME would not have been enough. This bridge sets RA=0, so a stub
handed a dangling CNAME has been told in the same breath that nobody will
chase it; systemd-resolved reports that as a name with no address.
buildChainResponse() emits the leaf alongside it, best-effort, so a slow
upstream costs the extra record and never the answer.

DoH gets the same treatment. Its own comment said a name that resolves
over the bridge and not over DoH is the failure that endpoint exists to
remove, and it carried the identical gap.

Verified against the live registry before any test was written:

    seo.rank.             30 IN CNAME dev.profullstack.com.
    dev.profullstack.com. 30 IN A     67.205.189.229
    alt.2600.            300 IN AAAA  2604:a880:400:d1:0:4:c3fe:1

One existing test changed rather than added to: it had recorded "a live
name pointed at a hostname is NODATA" as correct. It was the bug.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ralyodio
ralyodio merged commit ac3cf99 into main Aug 3, 2026
@ralyodio
ralyodio deleted the fix/resolver-address-answer branch August 3, 2026 14:42
@ralyodio
ralyodio restored the fix/resolver-address-answer branch August 3, 2026 14:42
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

87 finding(s)

HIGH/CRITICAL: 50 | MEDIUM: 37

Severity Rule Location
HIGH manifest-typosquat apps/pwa/package.json:19
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH secret-generic-credential apps/pwa/test/apikey-bearer-scheme.test.mjs:30
HIGH secret-generic-credential apps/pwa/test/apikey-mask.test.mjs:38
HIGH secret-generic-credential apps/pwa/test/apikey-reveal.test.mjs:35
HIGH secret-generic-credential apps/pwa/test/approvals-context.test.mjs:28
HIGH secret-generic-credential apps/pwa/test/approvals-credits.test.mjs:28
HIGH secret-generic-credential apps/pwa/test/approvals-notify.test.mjs:26
HIGH secret-generic-credential apps/pwa/test/approvals-resolve-race.test.mjs:20
HIGH secret-generic-credential apps/pwa/test/auth-form-email.test.mjs:29
HIGH secret-generic-credential apps/pwa/test/auth-form-email.test.mjs:33
HIGH secret-generic-credential apps/pwa/test/auth-page-error.test.mjs:36
HIGH secret-generic-credential apps/pwa/test/cli-device-token.test.mjs:28
HIGH secret-generic-credential apps/pwa/test/cli-pages-balance.test.mjs:32
HIGH secret-generic-credential apps/pwa/test/cli-token.test.mjs:28
HIGH secret-generic-credential apps/pwa/test/credits-pack.test.mjs:51
HIGH secret-generic-credential apps/pwa/test/credits-webhook-event-match.test.mjs:35
HIGH secret-generic-credential apps/pwa/test/credits-webhook.test.mjs:28
HIGH secret-generic-credential apps/pwa/test/csrf-input-escaping.test.mjs:31
HIGH secret-generic-credential apps/pwa/test/csrf-input-escaping.test.mjs:101
HIGH secret-generic-credential apps/pwa/test/logout-csrf.test.mjs:29
HIGH secret-generic-credential apps/pwa/test/moshpit-api-key.test.mjs:28
HIGH secret-generic-credential apps/pwa/test/moshpit-bulk-claim.test.mjs:23
HIGH secret-generic-credential apps/pwa/test/moshpit-claim-full-name.test.mjs:29
HIGH secret-generic-credential apps/pwa/test/moshpit-crawlable.test.mjs:28
HIGH secret-generic-credential apps/pwa/test/moshpit-ending-page.test.mjs:28
HIGH secret-generic-credential apps/pwa/test/moshpit-pins.test.mjs:22
HIGH secret-generic-credential apps/pwa/test/moshpit-pit-page.test.mjs:33
HIGH secret-generic-credential apps/pwa/test/moshpit-records-page.test.mjs:23
HIGH secret-generic-credential apps/pwa/test/moshpit-records.test.mjs:23
HIGH secret-generic-credential apps/pwa/test/moshpit-registry.test.mjs:20
HIGH secret-generic-credential apps/pwa/test/moshpit-related-endings.test.mjs:28
HIGH secret-generic-credential apps/pwa/test/moshpit-sales.test.mjs:16
HIGH secret-generic-credential apps/pwa/test/moshpit-search.test.mjs:74
HIGH secret-generic-credential apps/pwa/test/moshpit-terms.test.mjs:19
HIGH secret-generic-credential apps/pwa/test/moshpit-tlds-pagination.test.mjs:28
HIGH secret-generic-credential apps/pwa/test/passkey-register-duplicate.test.mjs:38
HIGH secret-generic-credential apps/pwa/test/require-auth-next.test.mjs:31
HIGH secret-generic-credential apps/pwa/test/require-auth-next.test.mjs:35
HIGH secret-generic-credential apps/pwa/test/sessions-output-seq.test.mjs:30
HIGH secret-generic-credential apps/pwa/test/sessions-paste.test.mjs:24
HIGH secret-generic-credential apps/pwa/test/sessions-stream-replay.test.mjs:34
HIGH secret-generic-credential apps/pwa/test/sessions.test.mjs:24
HIGH secret-generic-credential apps/pwa/test/signature.test.mjs:6
HIGH secret-generic-credential test/auth.test.mjs:13
HIGH secret-generic-credential test/auth.test.mjs:63
HIGH secret-generic-credential test/console-cookie-malformed.test.mjs:15
HIGH secret-generic-credential test/console.test.mjs:12
HIGH secret-generic-credential test/mirror.test.mjs:37
HIGH secret-generic-credential test/mirror.test.mjs:77

…and 37 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio ralyodio mentioned this pull request Aug 3, 2026
ralyodio added a commit that referenced this pull request Aug 3, 2026
Two resolver fixes, both of which look like working DNS from outside.

#268 — a name pointed at a host answered an authoritative NOERROR with
no records, which a client treats as final. seo.rank, chovy.hacker and
alt.2600 were all unreachable while google.com resolved, so the machine
looked healthy. RECORD_TYPES never consulted a published A or AAAA for
an address question, and targetAddress() returned null for a hostname
with nothing picking up after it.

#267 — `dns enable` reported that a bridge already on the port "is being
used as-is" and then started a second daemon anyway, which bound
alongside it and took the queries the note promised to the first.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@ralyodio
ralyodio deleted the fix/resolver-address-answer branch August 3, 2026 15:04
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