diff --git a/drill/doctor.sh b/drill/doctor.sh index 9f8ba33..464ef59 100755 --- a/drill/doctor.sh +++ b/drill/doctor.sh @@ -91,6 +91,15 @@ fi head_ "Profile — claude-dev (the NIC is the isolation contract)" if incus profile show claude-dev >/dev/null 2>&1; then + iso="$(incus profile device get claude-dev eth0 security.port_isolation 2>/dev/null)" + if [ "$iso" = "true" ]; then + ok "security.port_isolation = true — boxes cannot reach each other at L2" + else + no "security.port_isolation is NOT set — BOXES CAN REACH EACH OTHER" + inf "an L3 ACL cannot do this: two boxes on one bridge are on the same L2" + inf "segment, so their frames are switched, never routed past the ACL." + inf "fix: re-run ~/.local/share/claudebox/host/setup-host.sh" + fi for k in security.mac_filtering security.ipv4_filtering; do v="$(incus profile device get claude-dev eth0 "$k" 2>/dev/null)" if [ -z "$v" ]; then diff --git a/profiles/claude-dev.yaml b/profiles/claude-dev.yaml index 4b4850d..1273fdd 100644 --- a/profiles/claude-dev.yaml +++ b/profiles/claude-dev.yaml @@ -8,6 +8,17 @@ devices: type: nic network: claudenet name: eth0 + # Boxes must not reach each other. This is the mechanism that actually does + # it: the kernel bridge's port-isolation flag, which stops two isolated + # ports exchanging frames at L2. + # + # It is not an ACL rule, and it cannot be. Incus ACLs are L3/L4, and two + # boxes on one bridge are on the same L2 segment — their frames are switched + # between ports and never traverse the netfilter path an ACL lives on. That + # is why the ACL's drop on 10.0.0.0/8 (which contains claudenet) and its + # default ingress drop BOTH looked airtight while box→box was wide open: a + # live probe found box A's SYN arriving at box B and B answering with a RST. + security.port_isolation: "true" root: type: disk pool: default