#!/usr/bin/env python3 """walk_launcher.py — capability map E8/E9 walked on scratch 9202 (night 2026-10-28), front doors only: the household's launcher, then the guest link (/s/): enable, open it with NO session, a wrong token, rotate, and put the share back as found. The token is a capability secret: never printed (redacted to its length).""" import os, re, sys sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") os.environ.setdefault("SC", "/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/0b76c4d7-9b39-4e4e-869d-d0d2c6006331/scratchpad/rt-9202") os.environ.setdefault("TARGET", "9202") import box_walk as w EV = "/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/0b76c4d7-9b39-4e4e-869d-d0d2c6006331/scratchpad/night/p5" w.login() SC = os.environ["SC"] sess = open(f"{SC}/sess{os.getpid()}.txt").read().strip() csrf = open(f"{SC}/csrf{os.getpid()}.txt").read().strip() res = {} def get(path, auth=True): a = ["curl", "-sk", "-D", "-", "-H", w.HOSTHDR] if auth: a += ["-H", f"Cookie: {sess}"] r = w.sh(a + [f"{w.BASE}{path}"]) head, _, body = (r.stdout or "").replace("\r\n", "\n").partition("\n\n") m = re.match(r"HTTP/\S+ (\d+)", head) return (m.group(1) if m else "?"), head, body def post(path): r = w.sh(["curl", "-sk", "-D", "-", "-o", "/dev/null", "-H", w.HOSTHDR, "-H", f"Cookie: {sess}", "-X", "POST", "--data-urlencode", f"_csrf={csrf}", f"{w.BASE}{path}"]) loc = [l for l in (r.stdout or "").split("\n") if l.lower().startswith("location:")] return (r.stdout or "").split("\n")[0].strip(), (loc[0].strip() if loc else "") def token_of(body): m = re.search(r'id="share-link-input"[^>]*value="https?://[^"]*/s/([^"/?#]+)"', body) return m.group(1) if m else None code, _, body = get("/launcher") apps = sorted(set(re.findall(r'https://([a-z0-9-]+)\.' + re.escape(w.DOMAIN), body)) - {"felhom"}) res["launcher"] = {"http": code, "app_links": apps} w.say(f"E8 launcher: http={code}, app links {apps}") before = token_of(body) if os.environ.get("SHARE_WAS_OFF") == "1": # the first run switched it on, then stopped (its regex missed the link) before = None res["share_on_before"] = bool(before) w.say(f"E9 share on before the walk: {bool(before)}") if not before: w.say("enable: " + str(post("/launcher/share/enable"))) _, _, body = get("/launcher") tok = token_of(body) assert tok, "no share link after enable" w.say(f"E9 the guest link exists (token length {len(tok)})") g, head, gbody = get(f"/s/{tok}", auth=False) admin_links = sorted(set(re.findall(r'href="(/(?:stacks|settings|backups|storage|debug|launcher/share)[^"]*)"', gbody))) gapps = sorted(set(re.findall(r'https://([a-z0-9-]+)\.' + re.escape(w.DOMAIN), gbody)) - {"felhom"}) res["guest"] = {"http": g, "app_links": gapps, "admin_links": admin_links, "set_cookie": "set-cookie" in head.lower()} w.say(f"E9 guest link with NO session: http={g}, app links {gapps}, admin links {admin_links}") bad, _, badbody = get("/s/" + "x" * len(tok), auth=False) unk, _, unkbody = get("/no-such-route-r1256", auth=False) res["wrong_token"] = {"http": bad, "same_as_unknown_route": badbody == unkbody, "unknown_http": unk} w.say(f"E9 a wrong token: http={bad}; byte-identical to an unknown route: {badbody == unkbody} (unknown route http={unk})") w.say("rotate: " + str(post("/launcher/share/rotate"))) _, _, body = get("/launcher") tok2 = token_of(body) o, _, _ = get(f"/s/{tok}", auth=False) n, _, _ = get(f"/s/{tok2}", auth=False) if tok2 else ("none", "", "") res["rotate"] = {"old_link_http": o, "new_link_http": n, "token_changed": tok2 != tok} w.say(f"E9 after rotate: old link http={o}, new link http={n}, changed={tok2 != tok}") if not before: w.say("disable (as found): " + str(post("/launcher/share/disable"))) d, _, _ = get(f"/s/{tok2}", auth=False) res["after_disable_http"] = d w.say(f"E9 after disable: the link answers http={d}") import json json.dump(res, open(os.path.join(EV, "launcher-walk.json"), "w"), indent=1) open(os.path.join(EV, "launcher-walk.log"), "w").write("\n".join(w.LOG) + "\n")