Files
felhom.eu/documentation/audits/catalog-sweep-2026-10-24/tools/escrow.py
T
admin 67053335d1
gates / gates (push) Successful in 13m17s
Catalog sweep 2026-10-24: all 65 catalog apps on a fresh box (52 work, 10 with a problem, 0 broken); register 208 -> 217 (R-1269..R-1277 opened; notes on R-739, R-1267); STATUS + D46; map B1 re-checked; evidence
Every catalog app installed, used, backed up, removed keeping data, reinstalled, restored and removed with data on a
fresh box from the public ISO (demo-hp VM 9360, Tester4), endpoint-level. Worst: a reinstall's capture rewrites the
kept unit so its restore breaks the app (R-1269, P2), the restore still reports success (R-1270, P2), and „use my kept
data" loaded another box's future-dated database (R-1271, P2). Drill catalog fixes for the morning list (n8n usage
data; OpenGist 256M, proven by a cgroup probe). VM 9360 and LXC 9361 destroyed; hub host Tester4-aa9466 left for the
operator. REPORT-catalog-sweep-2026-10-24.md.
2026-10-25 00:13:05 +02:00

81 lines
3.8 KiB
Python

#!/opt/pw/bin/python
"""escrow.py — the household's recovery code ceremony in real Chromium (rehearsal 2026-10-20, B2).
Env: PW (the dashboard password). The recovery code is read off the page into /root/work/rc (0600) — never printed —
and DooPlex moves it into `sec` and shreds the file. Before every screenshot the code block is replaced by black
bars (the page keeps the code in a JS closure, so the check still works).
usage: escrow.py OUTPREFIX [--size phone|desktop]
"""
import os, re, sys, time
from playwright.sync_api import sync_playwright
out = sys.argv[1]
size = sys.argv[sys.argv.index("--size") + 1] if "--size" in sys.argv else "phone"
vp = {"phone": dict(viewport={"width": 390, "height": 844}, device_scale_factor=2, is_mobile=True, has_touch=True),
"desktop": dict(viewport={"width": 1366, "height": 768})}[size]
T0 = time.time()
def say(*a):
print(f"+{time.time() - T0:6.1f}s", *a, flush=True)
def shot(pg, name):
pg.evaluate("""() => { const b = document.getElementById('code-block'); if (b && b.textContent) b.textContent = '█████-█████-█████-█████-█████-█████-█████-█████-█████-█████';
document.querySelectorAll('input').forEach(e => { if (e.type !== 'checkbox') e.value = ''; }); }""")
pg.screenshot(path=f"{out}-{name}-{size}-hu.png", full_page=True)
with sync_playwright() as p:
b = p.chromium.launch(executable_path="/usr/bin/chromium", args=["--no-sandbox"])
ctx = b.new_context(locale="hu-HU", timezone_id="Europe/Budapest", ignore_https_errors=True,
storage_state="/root/work/state-dash.json", **vp)
pg = ctx.new_page()
pg.goto("https://felhom.tester4.test/backup/escrow", wait_until="networkidle")
pg.wait_for_selector("#warn-card", state="visible", timeout=60000)
pg.fill("#reauth-password", os.environ["PW"])
pg.click("#start-btn")
say("start pressed")
pg.wait_for_timeout(1500)
shot(pg, "2-progress")
try:
pg.wait_for_selector("#reveal-card, #void-card", state="visible", timeout=300000)
except Exception:
say("no reveal within 300 s:", re.sub(r"\s+", " ", pg.inner_text("#progress-card"))[:400])
shot(pg, "2b-stuck")
sys.exit(2)
if pg.is_visible("#void-card"):
say("VOID card:", re.sub(r"\s+", " ", pg.inner_text("#void-card"))[:400])
shot(pg, "3-void")
sys.exit(3)
say("reveal card shown")
shot(pg, "3-reveal-gate")
pg.click("#reveal-btn")
pg.wait_for_selector("#reveal-shown", state="visible", timeout=60000)
code = pg.inner_text("#code-block").strip()
fd = os.open("/root/work/rc", os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
os.write(fd, code.encode())
os.close(fd)
words = code.split("-")
say(f"code read off the page: {len(words)} words, {len(code)} characters (stored 0600, not printed)")
label = pg.inner_text("#verify-label")
pos = [int(x) for x in re.findall(r"\d+", label)][:2]
say("the page asks for words at positions", pos, "| label:", label)
shot(pg, "4-code-shown-REDACTED")
pg.fill("#verify-w1", words[pos[0] - 1])
pg.fill("#verify-w2", words[pos[1] - 1])
pg.click("#verify-btn")
pg.wait_for_timeout(4000)
if pg.is_visible("#verify-error"):
say("verify error:", pg.inner_text("#verify-error"))
pg.wait_for_selector("#finish-block", state="visible", timeout=60000)
say("words accepted; finish block:", re.sub(r"\s+", " ", pg.inner_text("#finish-block"))[:400])
shot(pg, "5-words-ok")
pg.check("#finish-check")
pg.click("#finish-btn")
pg.wait_for_load_state("networkidle")
pg.wait_for_timeout(3000)
say("after finish:", pg.url, re.sub(r"\s+", " ", pg.inner_text("main") if pg.locator("main").count() else pg.inner_text("body"))[:900])
shot(pg, "6-after-finish")
b.close()