diff --git a/drill/RUNS.md b/drill/RUNS.md index 18c1a59..72d94a0 100644 --- a/drill/RUNS.md +++ b/drill/RUNS.md @@ -5,7 +5,11 @@ how to diagnose the next stall without starting from zero. Append a section per run; keep the traps table current — it is the part that saves time. The audit this fed, [#15](https://github.com/heavy-duty/claudebox/issues/15), is -**complete** (run 10, 48/49). +**complete** (run 10, 48/49) — and its headline finding is **fixed and +re-measured**: sibling isolation shipped in #37/#38, and runs 11–13 hold at +**zero failures**, run 13 from a bare host (full teardown → rebuild → drill, +47/47). [#16](https://github.com/heavy-duty/claudebox/issues/16) closed on that +evidence. ## The audit's answer @@ -14,8 +18,8 @@ The audit this fed, [#15](https://github.com/heavy-duty/claudebox/issues/15), is | A1/A5 egress + gateway DNS | PASS | | A2 box → host | dropped | | A2 box → RFC1918 | dropped | -| **A3 sibling isolation** | 🔴 **FAIL — tcp REFUSED, i.e. the packet ARRIVED.** Boxes reach each other. #12's central claim was wrong; **#16 is a fix, not a formalization** | -| A4 DNS enumeration | LEAKS — a box resolves its sibling by name and address | +| **A3 sibling isolation** | run 10: 🔴 **FAIL — tcp REFUSED, i.e. the packet ARRIVED.** #12's central claim was wrong; #16 became a fix, not a formalization. **Fixed** (`security.port_isolation` + nft bridge drop, #37/#38); runs 11–13: **BLOCKED — tcp dropped + no icmp reply** | +| A4 DNS enumeration | run 10: LEAKS. **Fixed** (`dns.mode=none` shipped); runs 11–13: **blocked** — and the resolver is pinned so tailnet/split-DNS names don't resolve either (#33/#45) | | A6 IPv6 off | `none` ✓ | | A7 inbound host → box | dropped | | B1 `@internal` on a bridge ACL | REJECTED — `Unsupported nftables subject` ⇒ #16 derives the subnet | @@ -24,8 +28,10 @@ The audit this fed, [#15](https://github.com/heavy-duty/claudebox/issues/15), is | B4 `config get` unset key | empty + exit 0 ⇒ #17 must use `${var:-}` | | B5 L2 filtering | 🔴 `ipv4_filtering` **BREAKS the box** — design veto (measured on a healthy baseline) | -**The headline:** the tool's contract — *"a box reaches the public internet and -nothing else"* — is **false today**. It also reaches every other box on the host. +**The headline, then:** the tool's contract — *"a box reaches the public +internet and nothing else"* — was **false**: a box also reached every other box +on the host. **The headline, now:** the contract holds, and it is *measured* — +47/47 from a bare host (run 13), every layer probed from inside the boxes. ## Findings in claudebox (not in the drill) @@ -121,6 +127,33 @@ Read this before adding a probe. Every one of these cost a run. instant; an unreachable host burns the timeout. This is the same disease as every other trap here — trusting a proxy for the fact instead of the fact. +13. **A TTY on stdin makes `incus exec` interactive — and then `timeout` + cannot kill it.** The doctor's probes ran bare `timeout N incus exec …` + with the operator's terminal as stdin: exec attached it in raw mode, the + 20s TERM never took (no `-k` escalation), and **Ctrl-C was forwarded into + the box as a keystroke** — a 15-minute hang the operator could only end by + killing the shell. The drill had already learned this in #22 (`exec_in` + pins stdin, escalates with `-k 5`); the doctor's probe section was added + later and never inherited the cure. Every `incus exec` in a script gets + `