R-890: the test box (scratch 9202) may seed vaultwarden through its admin invite, inside the box
gates / gates (push) Successful in 7s

`09` §3 decision 149. box_admin_seed_allowed(): not the bench, FELHOM_BOX_ADMIN_SEED=1,
demo-hp/9202 only, an app this run installed, the box on the drill catalog. The token is read
inside the box and handed to curl on stdin; only HTTP codes come back.
Tests: BoxAdminSeedGuard (red-proved).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-06 13:39:48 +02:00
parent 65130c6c03
commit 6b4877d556
4 changed files with 185 additions and 3 deletions
+6
View File
@@ -1,3 +1,9 @@
## 2026-10-06 (afternoon) — the test box may seed vaultwarden through its admin page (R-890)
**What runs on a box changed:** nothing. Test tools only.
- R-890 (`09` §3 decision 149): the box walk may now seed vaultwarden through its admin invite on a TEST box, as the bench does. A guard refuses unless all five hold: not the bench venue, FELHOM_BOX_ADMIN_SEED=1, the walk targets scratch guest 9202 on demo-hp (never 9201; the Tester 1 box is allowed by the ruling but the walk has no route to it), THIS run installed the app (box_walk now records its installs), and the box's own controller.yaml names the drill catalog. The invite runs INSIDE the box: the token is read from the container's environment into a shell variable and handed to curl on stdin, the admin cookie lives in a 0600 file that is shredded, and only HTTP codes come back — the token never leaves the box. Tests: BoxAdminSeedGuard (red-proved: a guard that always allows fails three tests).
## 2026-10-06 (midday) — the operator's ten answers: two page sentences, the immich marker list, a bench-only vaultwarden seed (R-747, R-774, R-734, R-624)
**What runs on a box changed:** one first-step sentence each on mealie's and Karakeep's page (hu + en). Nothing else a box runs.
+4
View File
@@ -233,6 +233,9 @@ DRIVE = "/mnt/felhom-drives/scratch_hdd/userdata"
# password back off the box — the household sees it once. So the value the harness itself
# generated is kept here for the life of the run, and nowhere else.
GENERATED = {}
# The apps THIS run installed (deploy() adds one on its 202). The test-box admin seed refuses an app not in it: an app
# the walk merely reused may be a household's (upgrade_fixtures_box.box_admin_seed_allowed, R-890).
DEPLOYED_THIS_RUN = set()
def deploy_values(name, sub):
@@ -297,6 +300,7 @@ def deploy(name, sub, extra_values=None):
say(f" [1] deploy -> {code} {str(d)[:120]}")
if code != "202":
return False
DEPLOYED_THIS_RUN.add(name)
# WAIT FOR `deployed`, NOT FOR `running`. Measured 2026-09-21 on tandoor: docker reported the
# container `healthy` while the controller's own state read `unhealthy` — a gate on `running`
# alone therefore times out on an app that is up. The state is RECORDED rather than required;
+91
View File
@@ -259,6 +259,97 @@ class BenchAdminSeedGuard(unittest.TestCase):
self.assertFalse(any(ADMIN in s or "jwt.admin.session" in s for s in self.said), "a secret was printed")
class FakeBoxWalk(FakeVenue):
"""box_walk's interface on a test box: no VENUE, HP/GUEST, the run's installs, and guest() that answers like the
in-box admin invite script would. Records every script sent into the box."""
def __init__(self, hp="demo-hp", guest="9202", deployed=("vaultwarden",), drill=True, invite="RESULT signin=200 session=yes invite=200"):
FakeVenue.__init__(self, False)
self.GENERATED = {}
self.HP, self.GUEST, self.DEPLOYED_THIS_RUN = hp, guest, set(deployed)
self.drill, self.invite, self.scripts = drill, invite, []
def guest(self, script, timeout=600):
self.scripts.append(script)
if "controller.yaml" in script:
return ("git:\n repo_url: https://gitea.dooplex.hu/admin/%s.git\n"
% ("app-catalog-drill" if self.drill else "app-catalog-felhom.eu"))
if "/admin/invite" in script:
m = __import__("re").search(r'"email":"([^"]+)"', script)
if self.invite.endswith("invite=200"):
self.invited.add(m.group(1))
return "some noise\n" + self.invite + "\n"
return ""
class BoxAdminSeedGuard(unittest.TestCase):
"""R-890 (`09` §3 decision 149): the admin seed on a TEST BOX — scratch 9202 on demo-hp, an app this run installed,
the box on the drill catalog, the run opted in. RED-PROOF (REPORT): make box_admin_seed_allowed return (True, "") —
test_each_condition_alone_refuses and test_a_household_shaped_box_is_never_seeded fail."""
ENV = {"FELHOM_BOX_ADMIN_SEED": "1"}
def allowed(self, w, env):
with mock.patch.dict(os.environ, env, clear=False):
if "FELHOM_BOX_ADMIN_SEED" not in env:
os.environ.pop("FELHOM_BOX_ADMIN_SEED", None)
return fxbox.box_admin_seed_allowed(w, "vaultwarden")
def test_all_conditions_allow(self):
self.assertEqual(self.allowed(FakeBoxWalk(), self.ENV), (True, ""))
def test_each_condition_alone_refuses(self):
cases = [(FakeVenue(True), self.ENV, "bench venue"),
(FakeBoxWalk(), {}, "FELHOM_BOX_ADMIN_SEED"),
(FakeBoxWalk(), {"FELHOM_BOX_ADMIN_SEED": "yes"}, "FELHOM_BOX_ADMIN_SEED"),
(FakeBoxWalk(guest="9201"), self.ENV, "not a test box"),
(FakeBoxWalk(hp="demo-felhom"), self.ENV, "not a test box"),
(FakeBoxWalk(deployed=()), self.ENV, "did not install"),
(FakeBoxWalk(drill=False), self.ENV, "drill catalog")]
for w, env, want in cases:
ok, why = self.allowed(w, env)
self.assertFalse(ok, want)
self.assertIn(want, why)
def seed(self, w, env):
with mock.patch.dict(os.environ, env, clear=False):
if "FELHOM_BOX_ADMIN_SEED" not in env:
os.environ.pop("FELHOM_BOX_ADMIN_SEED", None)
os.environ.pop("FELHOM_BENCH_ADMIN_SEED", None)
fx_ = fxbox.Vaultwarden()
return fx_, fx_.seed(w, "vault", lambda *a: None)
def test_the_test_box_seeds_through_the_invite_inside_the_box(self):
w = FakeBoxWalk()
_, got = self.seed(w, self.ENV)
self.assertIsNotNone(got)
self.assertEqual([c["path"] for c in w.calls], ["/identity/accounts/register", "/identity/accounts/register"])
inv = [x for x in w.scripts if "/admin/invite" in x]
self.assertEqual(len(inv), 1)
# the token is read inside the box and handed to curl on stdin; the cookie file is shredded
self.assertIn("printenv ADMIN_TOKEN", inv[0])
self.assertIn("token@-", inv[0])
self.assertIn('shred -u "$H"', inv[0])
self.assertIn(got["email"], inv[0])
def test_a_household_shaped_box_is_never_seeded(self):
for w in (FakeBoxWalk(guest="9201"), FakeBoxWalk(deployed=()), FakeBoxWalk(drill=False)):
fx_, got = self.seed(w, self.ENV)
self.assertIsNone(got)
self.assertFalse([x for x in w.scripts if "/admin/invite" in x], "the admin route was tried")
self.assertIn("NOT tried", fx_.tried)
def test_a_refused_invite_is_inconclusive(self):
w = FakeBoxWalk(invite="RESULT signin=401 session=no")
fx_, got = self.seed(w, self.ENV)
self.assertIsNone(got)
self.assertIn("test-box admin invite", fx_.tried)
def test_only_a_drill_address_is_invited(self):
with self.assertRaises(ValueError):
fxbox.box_admin_invite(FakeBoxWalk(), 'x"}; rm -rf /; {"@gate.invalid')
class SecretHygiene(unittest.TestCase):
"""The run's secrets: .env 0600 and shredded, every evidence file redacted. RED-PROOF (REPORT): make redact_tree
return [] without rewriting — test_evidence_files_are_redacted fails."""
+84 -3
View File
@@ -304,6 +304,75 @@ def bench_admin_seed_allowed(w):
return True, ""
# --- R-890 (`09` §3 decision 149): the TEST BOXES may seed vaultwarden through its admin route too ---------------------
BOX_ADMIN_SEED_ENV = "FELHOM_BOX_ADMIN_SEED" # the run's explicit opt-in on a box walk
# The test boxes the walk can reach, as (Proxmox host, guest). Scratch 9202 only: box_walk.py drives guests on demo-hp
# alone (its HP), and 9201 there is demo-hp's household-shaped guest — a demo box's household apps are never seeded
# through their admin (decision 149). The Tester 1 box is allowed by the ruling but the walk has no route to it; add
# it here the day the walk can run there.
BOX_ADMIN_SEED_GUESTS = {("demo-hp", "9202")}
DRILL_CATALOG_MARK = "app-catalog-drill" # a test run points the box at the drill catalog (`09` §6.5)
def box_admin_seed_allowed(w, app):
"""(True, "") only on a TEST BOX walk; (False, why) everywhere else. ALL FIVE must hold:
1. not the bench venue (the bench has its own guard, bench_admin_seed_allowed);
2. the run opted in: FELHOM_BOX_ADMIN_SEED=1;
3. the walk targets a listed test box (BOX_ADMIN_SEED_GUESTS) — never 9201, never a household box;
4. THIS run installed the app (box_walk.deploy records it): an app the walk merely reused may be someone's;
5. the box itself says it is on the drill catalog (its controller.yaml names it) — a household box never is.
Pinned by scripts/test_upgrade_bench.py (BoxAdminSeedGuard: each condition alone refuses)."""
if getattr(w, "VENUE", None) == "bench":
return False, "the bench venue (its own guard decides there)"
if os.environ.get(BOX_ADMIN_SEED_ENV) != "1":
return False, "%s=1 is not set for this run" % BOX_ADMIN_SEED_ENV
where = (getattr(w, "HP", None), str(getattr(w, "GUEST", None)))
if where not in BOX_ADMIN_SEED_GUESTS:
return False, "%s guest %s is not a test box" % where
if app not in (getattr(w, "DEPLOYED_THIS_RUN", None) or set()):
return False, "this run did not install %s (a reused app may be a household's)" % app
conf = w.guest("grep -A3 '^git:' /var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml")
if DRILL_CATALOG_MARK not in (conf or ""):
return False, "the box is not on the drill catalog — not a test run"
return True, ""
# Runs INSIDE the guest. The admin token is read from the container's own environment into a shell variable and handed
# to curl on stdin (`printf` is a builtin: never on a command line); the admin session cookie lives in a 0600 file that
# is shredded. Only HTTP codes come back. The cookie is sent by hand because vaultwarden marks it Secure (its DOMAIN is
# https) and the call goes to the container's own address over plain http.
BOX_ADMIN_INVITE_SH = r"""set -u
c=vaultwarden
ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' "$c" 2>/dev/null | awk '{print $1}')
[ -n "$ip" ] || { echo "RESULT noip"; exit 0; }
T=$(docker exec "$c" printenv ADMIN_TOKEN 2>/dev/null)
[ -n "$T" ] || { echo "RESULT notoken"; exit 0; }
H=$(mktemp); chmod 600 "$H"
code=$(printf %s "$T" | curl -s -o /dev/null -D "$H" -w '%{http_code}' --max-time 30 --data-urlencode token@- "http://$ip:80/admin")
T=
ck=$(grep -i '^set-cookie: *VW_ADMIN=' "$H" | head -1 | sed -e 's/^[Ss]et-[Cc]ookie: *//' -e 's/;.*//' | tr -d ' ')
if [ -z "$ck" ]; then shred -u "$H"; echo "RESULT signin=$code session=no"; exit 0; fi
printf 'Cookie: %s
' "$ck" > "$H"; ck=
inv=$(curl -s -o /dev/null -w '%{http_code}' --max-time 30 -H @"$H" -H 'Content-Type: application/json' --data '{"email":"__EMAIL__"}' "http://$ip:80/admin/invite")
shred -u "$H"
echo "RESULT signin=$code session=yes invite=$inv"
"""
def box_admin_invite(w, email):
"""Invite `email` through vaultwarden's admin page, inside the test box (see BOX_ADMIN_INVITE_SH). Returns
(signin code, session yes/no, invite code) as the box printed them, or None when it printed no RESULT line."""
if not re.fullmatch(r"[a-z0-9.-]+@gate\.invalid", email):
raise ValueError("only a drill address is ever invited")
out = w.guest(BOX_ADMIN_INVITE_SH.replace("__EMAIL__", email))
m = re.search(r"^RESULT (.*)$", out or "", re.M)
if not m:
return None
kv = dict(p.split("=", 1) for p in m.group(1).split() if "=" in p)
return kv.get("signin", m.group(1)), kv.get("session", "no"), kv.get("invite", "")
def _curl_with_header_file(w, sub, path, header, *extra, **kw):
"""w.app_curl with ONE secret header read by curl from a 0600 temp file (`-H @file`), so the value is never
on a command line; the file is overwritten and removed afterwards, whatever happens."""
@@ -377,9 +446,17 @@ class Vaultwarden:
tried = f"POST /identity/accounts/register -> {code} (sign-up closed by design)"
ok, why = bench_admin_seed_allowed(w)
if not ok:
self.tried = tried + f"; the admin invite was NOT tried: {why}"
say(f" vaultwarden: {self.tried}")
return None
okb, whyb = box_admin_seed_allowed(w, "vaultwarden")
if not okb:
self.tried = tried + f"; the admin invite was NOT tried: bench — {why}; box — {whyb}"
say(f" vaultwarden: {self.tried}")
return None
got = box_admin_invite(w, email)
say(f" vaultwarden: test-box admin sign-in and invite (inside the box) -> {got}")
if not got or got[1] != "yes" or got[2] != "200":
self.tried = tried + f"; the test-box admin invite -> {got}"
return None
return self._invited(w, sub, email, key, tried, say)
token = (getattr(w, "GENERATED", {}).get("vaultwarden") or {}).get("ADMIN_TOKEN") or ""
if not token:
self.tried = tried + "; the bench generated no ADMIN_TOKEN for this run"
@@ -403,6 +480,10 @@ class Vaultwarden:
if code != "200":
self.tried = tried + f"; POST /admin/invite -> {code}"
return None
return self._invited(w, sub, email, key, tried, say)
def _invited(self, w, sub, email, key, tried, say):
"""The invited address registers through the household's own front door."""
rc, code, out = self._register(w, sub, email, key)
say(f" vaultwarden: invited registration http={code}")
if code not in ("200", "204"):