browserrot: Phase A-tier-1 site-side rotation engine (go-rod)

Deterministic, headless browser-driven password rotation:
discover change page via RFC 8615 / links, inject old+new over CDP
(no model in the loop), submit, and re-login to verify the new
password before commit. Implements rotate.Rotator so the mandatory
backup gate, verify-before-revoke ordering, and proof gate apply
unchanged; RevokeOld is a no-op (the site invalidates the old pw).

Proof-as-data per Site: the engine is LIVE-VM (real headless
Chromium vs a real local change-password form, via
lab-provision-browserrot.sh / TestIntegration_realBrowserRotation);
real-site selector tables stay UNPROVEN and nothing auto-registers
into production rotate yet. 14 packages, -race clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-07-18 12:43:56 -07:00
parent 6e3b39b393
commit 7a06d57bb3
8 changed files with 798 additions and 3 deletions
+1
View File
@@ -56,6 +56,7 @@ multipass exec incredigo-sbx -- bash -lc '
| `lab-provision-keepass.sh` | KeePassXC (`keepassxc-cli`) |
| `lab-provision-bitwarden.sh` + `vw-register.py` | Vaultwarden + `bw` CLI |
| `lab-provision-browsercsv.sh` + `csv-commit-probe.py` | Chrome/Firefox CSV (staging only) |
| `lab-provision-browserrot.sh` | Phase A-tier-1 site-side rotation engine (`internal/browserrot`) — real headless Chromium against a throwaway local change-password form (fake cred) |
| `tui-probe.py` | drives the `guide` Bubble Tea TUI under a pty |
## Custody / smoke
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# LIVE-VM proof for the Phase A-tier-1 site-side rotation ENGINE (internal/browserrot),
# inside the sandbox VM. FAKE credential only, against a throwaway in-process website —
# nothing real is touched.
#
# What this proves is the deterministic browser-drive machinery that would run against a
# real change-password page:
#
# * discover the change page via RFC 8615 /.well-known/change-password (real redirect)
# * inject the OLD + a freshly-minted NEW password into the form over CDP, with NO
# language model in the loop
# * submit and read the site's SUCCESS marker
# * VERIFY the new password authenticates by re-logging-in (hard rule 2), and confirm
# the OLD password no longer works (the rotation is real, not a no-op)
#
# The engine is exercised by the CHROME_BIN-gated integration test
# (TestIntegration_realBrowserRotation) driving a REAL headless Chromium. This is what
# earns browserrot's LIVE-VM proof level for the ENGINE; a specific real-site selector
# table stays UNPROVEN until it is run against that real site.
#
# It does NOT rotate any real account: only the user's own accounts, with explicit
# per-credential authorization, may ever be driven against a real provider.
set -euo pipefail
export PATH=/usr/local/bin:$PATH
# Locate a usable, NON-snap Chromium (snap chromium fights CDP + can't read /tmp).
find_chrome() {
for c in \
"${CHROME_BIN:-}" \
"$HOME"/.cache/ms-playwright/chromium-*/chrome-linux64/chrome \
"$HOME"/.cache/ms-playwright/chromium-*/chrome-linux/chrome \
/usr/bin/google-chrome /usr/bin/chromium /opt/google/chrome/chrome ; do
[ -n "$c" ] && [ -x "$c" ] && { echo "$c"; return 0; }
done
return 1
}
CHROME_BIN="$(find_chrome || true)"
if [ -z "${CHROME_BIN:-}" ]; then
echo "FAIL: no usable Chromium found. Install Chrome-for-Testing / google-chrome, or"
echo " set CHROME_BIN to a chromium executable (a Playwright-managed one is ideal)."
echo " hint: npx playwright install chromium"
exit 1
fi
echo "== using Chromium: $CHROME_BIN"
"$CHROME_BIN" --version 2>/dev/null | sed 's/^/ /' || true
# Run from the repo root (the dir containing go.mod).
cd "$(git -C "$(dirname "$0")" rev-parse --show-toplevel 2>/dev/null || echo "$(dirname "$0")/..")"
echo "== running the LIVE-VM browser-rotation proof =="
CHROME_BIN="$CHROME_BIN" go test -run TestIntegration_realBrowserRotation -v ./internal/browserrot/
echo
echo "LIVE-VM BROWSER-ROTATION ENGINE PROOF PASSED"