Found on the physical appliance bench (2026-08-15) after an operator's telnet pithead.local resolved to a public IPv6 address.
Evidence, from the live box:
hostname -I → 192.168.1.10 172.28.0.1 10.89.0.1 2001:db8::1 fd1c:…:71b8 — note the 2605: globally-routable address alongside the LAN and ULA ones.
- The generated Caddyfile's site line:
https://pithead.local, https://192.168.1.10, https://2001:db8::1, https://fd1c:…:71b8, https://localhost {
ss -tlnp → caddy LISTEN *:443 and *:80 (all interfaces).
- The host's only nft input chain is
policy accept with two DNS allowances — nothing filters inbound.
Mechanism (pithead, generate_caddyfile, ~L6909): on the appliance with no explicit dashboard.host, every address from hostname -I is added as a site. The intent is sound and documented — a headless box may be reached by mDNS name or by IP, and Caddy answers only addresses it was told about — but the enumeration does not distinguish private addresses from globally-routable ones.
Why it matters. IPv6 has no NAT: that address is world-addressable, so whether the dashboard is reachable from the internet depends entirely on the operator's router policy — which the product should never rely on. Behind that login sits the control channel: config edits, wallet changes, one-click upgrades. It also contradicts the documented posture (LAN-only dashboard; remote access is the Tor onion with client auth, deliberately, per docs/privacy.md and the #343 onion work). The login itself is bcrypt-gated and answers 401, so this is exposure rather than an open door — but an internet-facing login page behind a self-signed cert that users are trained to click through is not the threat model this product advertises.
Fix shape: restrict the appliance's auto-added sites to addresses that cannot be reached off-LAN — RFC1918 IPv4, ULA fd00::/8, link-local, loopback — and exclude globally-routable IPv6 (2000::/3); keep the mDNS name. If someone genuinely wants a global address served, that should be an explicit dashboard.host choice, which already wins and stays single-host. Worth a tier-1 test pinning the filter against a fixture address list like the one above (the real box's output is the perfect fixture), since this is exactly the kind of thing that silently regresses.
Also worth deciding as part of this: whether the appliance should carry a default inbound posture at all, rather than policy accept with the dashboard on *:443.
Pre-GA candidate for the v2 milestone — the appliance is the distribution where this is automatic and the operator never chose it.
Found on the physical appliance bench (2026-08-15) after an operator's
telnet pithead.localresolved to a public IPv6 address.Evidence, from the live box:
hostname -I→192.168.1.10 172.28.0.1 10.89.0.1 2001:db8::1 fd1c:…:71b8— note the2605:globally-routable address alongside the LAN and ULA ones.https://pithead.local, https://192.168.1.10, https://2001:db8::1, https://fd1c:…:71b8, https://localhost {ss -tlnp→ caddyLISTEN *:443and*:80(all interfaces).policy acceptwith two DNS allowances — nothing filters inbound.Mechanism (
pithead,generate_caddyfile, ~L6909): on the appliance with no explicitdashboard.host, every address fromhostname -Iis added as a site. The intent is sound and documented — a headless box may be reached by mDNS name or by IP, and Caddy answers only addresses it was told about — but the enumeration does not distinguish private addresses from globally-routable ones.Why it matters. IPv6 has no NAT: that address is world-addressable, so whether the dashboard is reachable from the internet depends entirely on the operator's router policy — which the product should never rely on. Behind that login sits the control channel: config edits, wallet changes, one-click upgrades. It also contradicts the documented posture (LAN-only dashboard; remote access is the Tor onion with client auth, deliberately, per docs/privacy.md and the #343 onion work). The login itself is bcrypt-gated and answers 401, so this is exposure rather than an open door — but an internet-facing login page behind a self-signed cert that users are trained to click through is not the threat model this product advertises.
Fix shape: restrict the appliance's auto-added sites to addresses that cannot be reached off-LAN — RFC1918 IPv4, ULA
fd00::/8, link-local, loopback — and exclude globally-routable IPv6 (2000::/3); keep the mDNS name. If someone genuinely wants a global address served, that should be an explicitdashboard.hostchoice, which already wins and stays single-host. Worth a tier-1 test pinning the filter against a fixture address list like the one above (the real box's output is the perfect fixture), since this is exactly the kind of thing that silently regresses.Also worth deciding as part of this: whether the appliance should carry a default inbound posture at all, rather than
policy acceptwith the dashboard on*:443.Pre-GA candidate for the v2 milestone — the appliance is the distribution where this is automatic and the operator never chose it.