#!/usr/bin/env bash # drill.sh — end-to-end drill for box, against a real Incus. # # ⚠ DESTRUCTIVE, AND MEANT TO BE. Run it on a THROWAWAY host you can format. # It installs Incus, rewrites the host's firewall rules, installs a systemd # unit, and creates and deletes instances. Never run it on a machine you care # about. # # bash drill/drill.sh # asks first # bash drill/drill.sh --yes # no prompt (CI, or you've read it) # bash drill/drill.sh --ref main # drill a different branch of the repo # bash drill/drill.sh --keep-boxes # leave the boxes up to poke at # # Four phases: # A. Incus semantics — the assumptions box is built on, probed directly. # These were only ever verified against a stub. # B. The box surface — the whole CLI, end to end, including the boundary. # C. Isolation baseline — does the trust boundary actually hold? (#15 section A) # D. Hardening rehearsal — #16's proposed changes, applied live and re-probed # (#15 section B). FAILs here are design vetoes, not code bugs. # # Exit 0 = every check passed. The summary ends with a block of audit answers # to paste into heavy-duty/claudebox#15. # # The file is one long 'probe && ok "..." || no "..."'. ok/no always return 0, so # the C-may-run-when-A-is-true trap SC2015 warns about cannot fire here. # shellcheck disable=SC2015 # # NOT -e: a failing check is data, not a crash. NOT pipefail: half the checks # are 'refusal 2>&1 | grep -q text' where the refusal exits 1/2 BY DESIGN, and # 'grep -q' SIGPIPEs the left side on early match — pipefail turned both into # false FAILs on the first live run. The pipeline verdict must be grep's alone. set -u REPO="${BOX_REPO:-heavy-duty/claudebox}" REF="${BOX_REF:-main}" YES=0; KEEP=0 SELF="$(readlink -f "$0")" while [ $# -gt 0 ]; do case "$1" in --yes|-y) YES=1; shift ;; --keep-boxes) KEEP=1; shift ;; --repo) REPO="$2"; shift 2 ;; --ref) REF="$2"; shift 2 ;; --in-group) shift; break ;; # internal: see below -h|--help) sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "drill: unknown option: $1" >&2; exit 2 ;; esac done pass=0; fail=0; findings=(); audit=() ok() { printf ' \033[32mPASS\033[0m %s\n' "$*"; pass=$((pass + 1)); } no() { printf ' \033[31mFAIL\033[0m %s\n' "$*"; fail=$((fail + 1)); findings+=("FAIL: $*"); } note() { printf ' \033[33mNOTE\033[0m %s\n' "$*"; findings+=("NOTE: $*"); } inf() { printf ' %s\n' "$*"; } phase(){ printf '\n\033[1m══ %s\033[0m\n' "$*"; } aud() { audit+=("$*"); } # an answer for the #15 audit wait_box() { # poll until exec answers (the VM agent can take a while), ~4 min # 2 min was too short: run 17's legacy box came up AFTER the window closed — # the drill called it dead and then every migration check on it passed. local b="$1" _i for _i in $(seq 1 120); do box exec "$b" -- true >/dev/null 2>&1 && return 0 sleep 2 done return 1 } # Read from inside a box WITHOUT ever hanging the drill. # # Two traps, both hit for real: # · 'box exec' becomes 'sudo -u