browserrot(gitea): M-B4 LIVE-REAL — first real site-side password rotation
ci / build-test (push) Waiting to run

Rotated the self-owned Gitea login password end-to-end via the new
lab/rung4-gitea-browser harness: backup-gated (Hard Rule 1) -> pre-change
login(OLD) -> change-form submit -> new stored to gopass -> verified by
browser re-login AND independent API re-auth. GiteaSite promoted to
ProofLiveReal (proof-as-data).

Three real-site fixes surfaced by the live-fire:
- sites.go: Gitea login + "Update Password" buttons carry no type=submit
  ATTRIBUTE (only the default DOM property), so CSS [type=submit] misses;
  select by class / the account form's sole button instead.
- rod.go: Click now waits for the post-submit navigation to settle before
  the success text is read (a remote round-trip, unlike the instant
  in-process test) — else Text() resolves against the pre-submit page.
- rod.go: new RodDriver.Insecure (--ignore-certificate-errors) for the
  self-signed / Tailscale-fronted host (browser analog of curl -k).

Suite green + -race clean; docs (BROWSER-ROTATION.md §9, BROWSERROT-PLAN.md)
and OVERSEER-STATUS updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-07-22 09:11:24 -07:00
parent ce8a5257a6
commit f8e93e892e
7 changed files with 292 additions and 24 deletions
+27 -1
View File
@@ -22,6 +22,13 @@ type RodDriver struct {
Bin string // chromium binary; "" lets go-rod find/download one
Headless bool // default true in production; false to watch it locally
Timeout time.Duration // per-operation timeout; defaults to 30s
// Insecure accepts self-signed / untrusted TLS certs (Chromium
// --ignore-certificate-errors). Needed for self-hosted targets behind a private
// CA or a Tailscale-fronted service (e.g. a personal Gitea on 100.x). This is the
// browser analog of `curl -k` in the rung harnesses; the connection is still TLS,
// we just don't verify the chain. Off by default — opt in per run for a host you
// own and whose cert you already trust out-of-band.
Insecure bool
}
// Name identifies the backend in logs/plans.
@@ -32,6 +39,11 @@ func (d *RodDriver) Name() string { return "go-rod" }
func (d *RodDriver) Open(ctx context.Context) (Session, error) {
l := launcher.New().Headless(d.Headless).
Set("no-sandbox").Set("disable-dev-shm-usage")
if d.Insecure {
// Accept the self-signed cert of a self-owned host. TLS still encrypts; only
// chain verification is skipped. Scoped to this launched browser instance.
l = l.Set("ignore-certificate-errors")
}
if d.Bin != "" {
l = l.Bin(d.Bin)
}
@@ -93,7 +105,21 @@ func (s *rodSession) Click(ctx context.Context, selector string) error {
if err != nil {
return err
}
return el.Click(proto.InputMouseButtonLeft, 1)
// A submit/login click triggers a navigation (login POST -> 302 -> dashboard,
// change-form POST -> flash). Against a remote host that round-trip has real
// latency, so we must wait for the NEW document to commit before returning —
// otherwise the next Text() read resolves against the PRE-submit page (the login
// form still showing) and mis-reports the outcome. Arm the navigation wait BEFORE
// clicking, then block until the network is almost idle. Bounded by the page
// timeout; if a click does not navigate this returns when the timeout elapses.
wait := s.page.Timeout(s.to).WaitNavigation(proto.PageLifecycleEventNameNetworkAlmostIdle)
if err := el.Click(proto.InputMouseButtonLeft, 1); err != nil {
return err
}
wait()
// Belt-and-suspenders: let any post-navigation DOM (flash render) settle.
_ = s.page.Timeout(s.to).WaitStable(300 * time.Millisecond)
return nil
}
func (s *rodSession) Text(ctx context.Context, selector string) (string, error) {