Files
felhom.eu/documentation/audits/catalog-sweep-2026-10-24/tools/bind.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

46 lines
2.4 KiB
Python

#!/opt/pw/bin/python
"""bind.py — the household's self-bind page, in real Chromium (rehearsal 2026-10-20, B2).
Env (handed in by brun.sh, never on a command line): BIND_TOKEN, PAIRING, PASS.
--typo : change ONE character of PASS before sending (a household's typo; the value is never printed)
Screenshots are taken BEFORE the fields are filled and AFTER the answer page loads — the passphrase field
is type=text, so a filled-form screenshot would show it. Prints the answer page's visible text.
usage: bind.py OUTPREFIX [--typo] [--size phone|desktop] [--lang hu|en] [--get-only]
"""
import os, re, sys
from playwright.sync_api import sync_playwright
out = sys.argv[1]; args = sys.argv[2:]
size = args[args.index("--size") + 1] if "--size" in args else "phone"
lang = args[args.index("--lang") + 1] if "--lang" in args else "hu"
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]
loc = {"hu": "hu-HU", "en": "en-US"}[lang]
url = "https://hub.felhom.eu/bind/" + os.environ["BIND_TOKEN"]
with sync_playwright() as p:
b = p.chromium.launch(executable_path="/usr/bin/chromium", args=["--no-sandbox"])
ctx = b.new_context(locale=loc, extra_http_headers={"Accept-Language": loc}, timezone_id="Europe/Budapest", **vp)
pg = ctx.new_page()
r = pg.goto(url, wait_until="networkidle")
pg.screenshot(path=f"{out}-1-open-{size}-{lang}.png", full_page=True)
print("GET", r.status, pg.title())
body = pg.inner_text("body")
print(re.sub(r"\s+", " ", body)[:600])
if "--get-only" in args or not pg.locator("#pairing_code").count():
b.close(); sys.exit(0)
pw = os.environ["PASS"]
if "--typo" in args: # swap one inner letter for its neighbour in the alphabet
i = len(pw) // 2
while not pw[i].isalpha():
i += 1
pw = pw[:i] + ("b" if pw[i] != "b" else "c") + pw[i + 1:]
pg.fill("#pairing_code", os.environ["PAIRING"])
pg.fill("#passphrase", pw)
with pg.expect_navigation(wait_until="networkidle"):
pg.click("button[type=submit]")
# blank both inputs before the screenshot, in case a re-rendered form echoes a value
pg.evaluate("document.querySelectorAll('input').forEach(e => e.value = '')")
pg.screenshot(path=f"{out}-2-answer-{size}-{lang}.png", full_page=True)
print("POST answer:", re.sub(r"\s+", " ", pg.inner_text("body"))[:600])
b.close()