fix: pin claudenet's resolver — a box's DNS is not a function of the host's VPN

The bridge's dnsmasq forwarded to whatever sat in the host's
/etc/resolv.conf at that moment. On a Tailscale host that is MagicDNS:
box DNS flapped with the tailnet (killing cold mints), and tailnet peer
names and split-DNS zones resolved from inside a box — name-level
reconnaissance of a private network, the same class as the sibling
enumeration dns.mode=none already closes.

setup-host.sh now sets raw.dnsmasq to no-resolv + pinned public
upstreams (BOX_DNS overrides the default 1.1.1.1 8.8.8.8), answering
the issue's three open questions from live measurement: raw.dnsmasq is
the lever (no first-class upstream key on the bridge; verified by
doctor --pin-dns followed by a box resolving), upstreams are a setting
with a sane default, and the pin is unconditional.

The doctor's unpinned-state messages now point at setup-host.sh as the
durable fix, keeping --pin-dns as the quick test.

Closes #33
This commit is contained in:
claude-hdb 2026-07-14 12:15:45 +00:00
parent 7efc8a171d
commit a2c0758ba6
2 changed files with 17 additions and 2 deletions

View file

@ -182,9 +182,10 @@ else
no "the host resolves via a CGNAT/Tailscale resolver ($hostns), and boxes INHERIT it — see issue #33" no "the host resolves via a CGNAT/Tailscale resolver ($hostns), and boxes INHERIT it — see issue #33"
inf "· box DNS breaks whenever the tailnet's resolver does (this is what kills cold mints)" inf "· box DNS breaks whenever the tailnet's resolver does (this is what kills cold mints)"
inf "· and tailnet names RESOLVE from inside a box, though its ACL blocks connecting to them" inf "· and tailnet names RESOLVE from inside a box, though its ACL blocks connecting to them"
inf "fix + test: bash drill/doctor.sh --pin-dns" inf "fix: re-run ~/.local/share/claudebox/host/setup-host.sh (it pins the resolver)"
inf " or quick-test the pin alone: bash drill/doctor.sh --pin-dns"
else else
inf "boxes inherit the host's resolver (unpinned). Fine while it is stable; see issue #33." inf "boxes inherit the host's resolver (unpinned — setup-host.sh pins this now; re-run it)"
fi fi
fi fi

View file

@ -46,6 +46,20 @@ incus network set claudenet security.acls=claude-isolate \
# forwarding for public names is unaffected (verified live). # forwarding for public names is unaffected (verified live).
incus network set claudenet dns.mode=none incus network set claudenet dns.mode=none
# A box's resolver must not be a function of the host's VPN posture (#33).
# The bridge's dnsmasq forwards to whatever sits in the HOST's /etc/resolv.conf
# at that moment. On a Tailscale/VPN host that is MagicDNS: box DNS flaps with
# the tailnet (this is what killed cold mints), and tailnet peer names and
# split-DNS zones RESOLVE from inside a box — name-level reconnaissance of a
# private network, the same shape as the sibling enumeration closed above.
# no-resolv detaches dnsmasq from the host's resolver entirely; server= pins a
# stable public upstream (override: BOX_DNS="ip ip…"). raw.dnsmasq is the
# lever — the bridge has no first-class upstream key. Verified live on the
# drill host: pin applied, box resolves, cold mint survives.
BOX_DNS="${BOX_DNS:-1.1.1.1 8.8.8.8}"
incus network set claudenet raw.dnsmasq \
"$(printf 'no-resolv\n'; for s in $BOX_DNS; do printf 'server=%s\n' "$s"; done)"
# Sibling isolation itself is NOT an ACL rule — an L3 ACL never sees frames # Sibling isolation itself is NOT an ACL rule — an L3 ACL never sees frames
# switched between two ports of one bridge. It lives in claudebox-firewall.sh # switched between two ports of one bridge. It lives in claudebox-firewall.sh
# as an nftables bridge-family rule. See the comment there; it is the reason # as an nftables bridge-family rule. See the comment there; it is the reason