diff --git a/drill/doctor.sh b/drill/doctor.sh index ee20e23..0dfced1 100755 --- a/drill/doctor.sh +++ b/drill/doctor.sh @@ -182,9 +182,10 @@ else 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 "· 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 - 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 diff --git a/host/setup-host.sh b/host/setup-host.sh index 644d968..6cb8434 100755 --- a/host/setup-host.sh +++ b/host/setup-host.sh @@ -46,6 +46,20 @@ incus network set claudenet security.acls=claude-isolate \ # forwarding for public names is unaffected (verified live). 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 # 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