Files
incredigo/lab/csv-commit-probe.py
leetcrypt ac0ff8e2af
ci / build-test (push) Has been cancelled
quarantine: scrub tailnet ref, add license, ship lab/ repro kit, refresh README for cohort
- untrack CLAUDE.md (tailnet leak) + stale HANDOFF.md; keep local via .gitignore
- add source-available LICENSE (attribution on fork, royalty on commercial use)
- add lab/ reproduction kit (fake-cred LIVE-VM/mock rotation POCs) + lab/README
- rewrite README to current status (22 drivers, 8 LIVE-VM/14 MOCK-ONLY/0 LIVE-REAL,
  247 tests) and carry the credential-handling safety rules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-17 22:40:12 -07:00

95 lines
3.1 KiB
Python

#!/usr/bin/env python3
# Drives `incredigo passwords ... commit --allow-csv` inside a real pty so the command
# sees a TTY and PAUSES at its "press Enter to shred" prompt. While the child is blocked,
# we inspect the tmpfs CSV it staged (location, header, contents), then release it and
# confirm the file was shredded. This is the only way to observe the otherwise-ephemeral
# tmpfs CSV, since headless commit shreds it immediately.
#
# argv: mgr export stage backup audit old1 old2 want_header pwcol
import os, pty, sys, select, re, subprocess, time
mgr, export, stage, backup, audit, old1, old2, want_header, pwcol = sys.argv[1:10]
pwcol = int(pwcol)
fail = 0
def out(m): print(" " + m, flush=True)
cmd = ["incredigo", "passwords", "--manager", mgr, "--export-path", export,
"--allow-csv", "commit", "--stage-in", stage, "--verified", "all",
"--backup-out", backup, "--audit-log", audit]
master, slave = pty.openpty()
proc = subprocess.Popen(cmd, stdin=slave, stdout=slave, stderr=slave, close_fds=True)
os.close(slave)
buf = b""
csv_path = None
deadline = time.time() + 30
while time.time() < deadline:
r, _, _ = select.select([master], [], [], 0.5)
if master in r:
try:
chunk = os.read(master, 4096)
except OSError:
break
if not chunk:
break
buf += chunk
m = re.search(rb'(/dev/shm/incredigo/incredigo-pw-[0-9]+\.csv)', buf)
if m and b'press Enter to shred' in buf:
csv_path = m.group(1).decode()
break
if proc.poll() is not None:
break
if not csv_path:
out("FAIL: no tmpfs CSV / shred prompt seen")
sys.stdout.write(buf.decode(errors="replace"))
sys.exit(1)
out("staged CSV: " + csv_path)
# (a) on tmpfs?
fst = subprocess.run(["stat", "-f", "-c", "%T", csv_path],
capture_output=True, text=True).stdout.strip()
if fst == "tmpfs":
out("OK: CSV is on tmpfs (%s)" % fst)
else:
out("FAIL: CSV not on tmpfs (%s)" % fst); fail = 1
# (b) correct per-browser header + (c) NEW pw present, OLD gone
with open(csv_path) as f:
lines = f.read().splitlines()
hdr = lines[0] if lines else ""
if hdr == want_header:
out("OK: header = " + hdr)
else:
out("FAIL: header=%s want=%s" % (hdr, want_header)); fail = 1
body = "\n".join(lines[1:])
for label, old in (("github", old1), ("gitlab", old2)):
if old in body:
out("FAIL: OLD %s pw present in CSV" % label); fail = 1
else:
out("OK: OLD %s pw absent from CSV" % label)
row1 = lines[1].split(",") if len(lines) > 1 else []
newpw = row1[pwcol - 1] if len(row1) >= pwcol else ""
if len(newpw) >= 16:
out("OK: row1 new pw is strong (len %d)" % len(newpw))
else:
out("FAIL: new pw too short (%d)" % len(newpw)); fail = 1
# (d) release the prompt -> shred
os.write(master, b"\n")
try:
proc.wait(timeout=15)
except subprocess.TimeoutExpired:
proc.kill(); out("FAIL: commit did not exit after release"); fail = 1
if not os.path.exists(csv_path):
out("OK: tmpfs CSV shredded (file gone)")
else:
out("FAIL: CSV still present after shred"); fail = 1
sys.exit(1 if fail else 0)