Skip to content

feat: browser-driven Hover auth (defeat Imperva) via GoCodeAlone/rod — v0.5.0#30

Merged
intel352 merged 20 commits into
mainfrom
feat/headless-browser-auth-2026-05-30T2030
Jun 1, 2026
Merged

feat: browser-driven Hover auth (defeat Imperva) via GoCodeAlone/rod — v0.5.0#30
intel352 merged 20 commits into
mainfrom
feat/headless-browser-auth-2026-05-30T2030

Conversation

@intel352
Copy link
Copy Markdown
Contributor

@intel352 intel352 commented Jun 1, 2026

Replaces Hover's broken cold-HTTP signin (always 401 behind Imperva ABP) with a real-Chrome auth path that runs Imperva's JS sensor, mints clearance, completes TOTP 2FA, and operates the account — validated against production Hover.

Proof (production, self-hosted CI)

go_http_reuse_viable=true domains=30 clearance_cookies=[__uzma __uzmb __uzme __uzmc __uzmd __ssds __ssuzjsr0] — go-rod fork clears Imperva, TOTP completes new-device 2FA, 30 real domains read. (gocodealone-dns run 26784365604.)

Architecture (hybrid)

  • Login in-browser via github.com/GoCodeAlone/rod (our maintained go-rod fork, ADR 0002): mint Imperva clearance + TOTP.
  • Reads reuse the clearance cookies in the existing Go http.Client (Imperva clears the session, not per-request).
  • Writes run in-browser (in-page fetch + CSRF) for TLS-fingerprint consistency.
  • All behind the private executionBackend seam — hoverclient.Client / gRPC / IaC provider contract unchanged. HTTP backend retained for injected-client unit tests.

Notable

  • New github.com/GoCodeAlone/rod fork (renamed module, Go 1.26.3, govulncheck/Dependabot/CodeQL clean) — upstream go-rod is stale since 2024.
  • Config: browser_path/browser_download/browser_headless/browser_profile_dir (+ HOVER_BROWSER_* env). Profile dir defaults under $XDG_STATE_HOME, gitignored.
  • Typed errors: ErrBotChallenge, ErrChromeUnavailable, ErrEmail2FARequired (email-2FA accounts can't headless-login — use TOTP or a pre-trusted profile).
  • Tests drive real go-rod against local httptest servers (skip when no Chrome → CI-safe). hover hygiene: Go 1.26.3 + x/net (vulns 7→2; remaining 2 are docker/docker SDK-transitive, no upstream fix).
  • Security review: docs/plans/2026-05-30-headless-browser-auth.security-review.md (PASS).

Rollback

Revert the plugin version pin to v0.4.2 (note: v0.4.2 cannot auth live Imperva — rollback = Hover automation disabled, not "old working behavior"). Change is additive behind the unchanged interface.

🤖 Generated with Claude Code

intel352 and others added 20 commits May 30, 2026 20:35
…ABP)

Replace cold-HTTP login with a real-Chrome (go-rod) session driver that runs
Imperva's JS sensor + mints clearance; full-browser flow (TLS/JS/cookie
consistency); system/cached/container Chrome; stealth. Login-only optimization
deferred to an empirical scope test with the verified test account.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
go-rod probe (ProbeLiveBrowserAuth) + opt-in live test. Launches Chrome,
strips navigator.webdriver, waits for Imperva clearance cookies, submits
signin (incl. 2FA path) via in-browser fetch, then probes whether a plain
Go http.Client can reuse the clearance for /api/domains (login-only
optimization signal). Live test skips unless HOVER_LIVE_TEST=1; never logs
secrets. go-rod is the driver picked by the both-drivers Imperva spike.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records that both playwright-go and go-rod cleared Imperva headless in the
scratch spike; go-rod picked for pure-Go runtime. De-risks Assumption #1.
Full authenticated login still gated on test-account verification. No
manifest change; no scope unlock required.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ged)

No maintained Go-native Imperva-bypass lib; SOTA stealth tools all
Python/Node (wrong language per ADR 0001). New 2026 signals (JA4 + UA-CH
consistency) reinforce full-browser default. CDP-protocol fingerprinting
flagged as most likely future-break vector for any CDP driver incl go-rod.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live gate caught a panic: go-rod KeepUserDataDir() only works on a managed
launcher; on a local launcher it panics (mustManaged). And Cleanup() deletes
UserDataDir. Drop KeepUserDataDir(); use Kill() in cleanup so the persistent
profile (and its Imperva clearance/cookies) survives across calls.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
go-rod reached auth.json (need_2fa), not the Imperva 401 → Assumption #1
validated. Records Hover's email-default 2FA, new-device challenge, and the
CI auth model (TOTP secret and/or persistent trusted profile).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps go directive 1.26.0->1.26.3 (fixes stdlib html/template/net/net/http
vulns GO-2026-4982/4980/4971/4918) and x/net 0.54.0->0.55.0 (GO-2026-5026).
gopls modernize: maps.Copy for manual copy loops. govulncheck 7->2 affecting
(remaining 2 are docker/docker via workflow SDK, no upstream fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records the maintained-fork decision (amends ADR 0001's driver source) and
backports the CI auth model + scope amendment (dep source change + prod
live-login proof via gh workflow on the self-hosted runner).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Repoints the go-rod CDP driver to the maintained fork
github.com/GoCodeAlone/rod v0.116.3 (ADR 0002). Build/vet/unit green;
govulncheck adds no new vulns (only the pre-existing docker/docker SDK
transitive remains, no upstream fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0 domains)

CI probe on self-hosted runner vs production: go-rod fork clears Imperva,
TOTP completes new-device 2FA, 30 domains read, go_http_reuse_viable=true.
Adopts login-only transport (browser login -> Go HTTP for API) per the
design's conditional, with full-browser fallback for the TLS-fingerprint risk
and write paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce executionBackend interface routing Login/ListDomains/etc. to
either httpBackend (injected http.Client — tests) or browserBackend
(nil http.Client — production Chrome path). NewClientWithOptions parses
explicit BrowserOptions; NewClient preserves backward-compat signature.
Provider Initialize parses browser_path/download/headless/profile_dir
config keys with HOVER_BROWSER_* env fallbacks via parseBrowserConfig.
browserBackend live ops return ErrBrowserBackendUnavailable (Task 3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ies to HTTP reads

Task 3: implement browserBackend.Login (Chrome launch via launchBrowserWithHandles,
navigator.webdriver strip, UA/AcceptLanguage stealth, waitForClearanceCookies,
submitBrowserSignin with TOTP, cookie handoff to c.http.Jar), typed errors
(ErrBotChallenge / ErrChromeUnavailable / ErrEmail2FARequired), read delegation
(ListDomains/GetDomain/ListRecords/GetDomainDelegation → HTTP backend after login),
writes still return ErrBrowserBackendUnavailable (Task 4). Ten new tests in
browser_backend_test.go drive real go-rod against local httptest servers.
Add .hover-browser-profile/ to .gitignore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…path)

Implement Task 4: CreateRecord, UpdateRecord, DeleteRecord, and
SetNameservers on browserBackend now execute in-page via Chrome fetch
(credentials:'include') so requests carry the live Imperva clearance
and Chrome TLS fingerprint. Generalise browserSigninFetch into reusable
browserFetchJSON/browserFetchWithHeaders helpers (probe + writes share
one in-page fetch path). SetNameservers extracts CSRF from the
control_panel page DOM in-browser then PUTs with X-CSRF-Token. Guards
b.browser == nil with a clear "not initialised" error when Login was
never run. All endpoints/payloads/typed-errors preserved from HTTP impls.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite README to document the real Chrome/go-rod auth architecture
(Imperva ABP bypass, hybrid browser-login + HTTP-read + in-browser-write
model, Chrome acquisition, BrowserOptions config keys + env aliases,
TOTP/email-2FA requirements, browser profile dir, typed errors). Remove
stale CSRF form-login description.

Bump both plugin.json manifests from 0.0.0 to 0.5.0 (behavioral minor
for the browser-auth backend).

Update iacserver_live_test.go: gate on HOVER_LIVE_TEST=1, source browser
opts via BrowserOptionsFromEnv + config keys from env, exercise the full
provider Initialize → EnumerateAll → Import → Status path (typed gRPC
server surface), skip cleanly when HOVER_LIVE_TEST is unset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 6: security review (PASS — no Critical/High; one tracked UA-derivation
resilience follow-up). Fixes Login mislabeling a cookie-read error as
ErrBotChallenge (only a clearance timeout is a challenge now).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 1, 2026 22:50
@intel352 intel352 merged commit 119730a into main Jun 1, 2026
5 checks passed
@intel352 intel352 deleted the feat/headless-browser-auth-2026-05-30T2030 branch June 1, 2026 22:55
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a browser-driven authentication path for Hover (Imperva ABP) using github.com/GoCodeAlone/rod, while preserving the existing provider/IaC public contract by routing operations through an internal backend seam (browser for login + writes; Go http.Client cookie reuse for reads).

Changes:

  • Added executionBackend seam and a Chrome/rod-based backend that mints Imperva clearance cookies, completes TOTP 2FA, and reuses cookies for HTTP reads.
  • Added browser runtime configuration (config keys + env aliases), typed errors, and extensive local httptest-driven rod tests plus opt-in live probes.
  • Updated docs/manifests for the new auth architecture and bumped release version to v0.5.0.

Reviewed changes

Copilot reviewed 27 out of 29 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
README.md Documents the new Chrome-based auth, hybrid read/write behavior, config/env options, and limitations.
plugin.json Bumps plugin manifest version to 0.5.0.
cmd/workflow-plugin-hover/plugin.json Bumps embedded plugin manifest version to 0.5.0.
.gitignore Ignores .hover-browser-profile/ local profile state.
go.mod Adds github.com/GoCodeAlone/rod and bumps Go/toolchain-related dependencies.
go.sum Records sums for rod and updated transitive deps.
pkg/hoverclient/options.go Adds ClientOptions for passing runtime options (browser config).
pkg/hoverclient/backend.go Introduces executionBackend interface + httpBackend delegator.
pkg/hoverclient/client.go Routes login and API methods via backend; adds NewClientWithOptions; keeps signatures stable.
pkg/hoverclient/client_test.go Adds tests asserting backend selection behavior and options preservation.
pkg/hoverclient/browser_options.go Defines BrowserOptions, defaults, env parsing, and profile-dir defaulting.
pkg/hoverclient/browser_probe.go Implements a live browser auth probe + cookie reuse viability probe.
pkg/hoverclient/browser_live_test.go Adds opt-in live test gated by HOVER_LIVE_TEST=1.
pkg/hoverclient/browser_backend.go Implements rod-driven login, typed errors, cookie handoff, and in-browser write operations.
pkg/hoverclient/browser_backend_test.go Adds local httptest + rod unit tests for login, error classification, cookie handoff, read delegation, close behavior.
pkg/hoverclient/browser_backend_write_test.go Adds local httptest + rod unit tests for in-browser DNS write operations and CSRF handling.
internal/provider.go Parses provider browser config and passes it into hoverclient.NewClientWithOptions.
internal/provider_test.go Adds tests covering provider browser config parsing and env aliases.
internal/iacserver.go Replaces manual map copies with maps.Copy.
internal/iacserver_live_test.go Expands live IaC tests to cover Initialize→EnumerateAll→Import→Status using browser backend.
docs/plans/2026-05-30-headless-browser-auth.md Captures the implementation plan for the browser auth work.
docs/plans/2026-05-30-headless-browser-auth.md.scope-lock Records scope-lock hash for plan verification.
docs/plans/2026-05-30-headless-browser-auth.plan-review-1.md Plan review notes and corrections.
docs/plans/2026-05-30-headless-browser-auth.alignment-report-1.md Design↔plan alignment report.
docs/plans/2026-05-30-headless-browser-auth-design.md Design doc for the Imperva/Chrome approach, risks, and validation.
docs/plans/2026-05-30-headless-browser-auth-design.adversarial-review-1.md Adversarial design review notes.
docs/plans/2026-05-30-headless-browser-auth.security-review.md Security review report for the headless-browser auth approach.
decisions/0001-real-browser-auth-for-imperva.md ADR documenting the decision to use a real browser to defeat Imperva ABP.
decisions/0002-fork-go-rod-for-maintenance-and-dep-control.md ADR documenting the fork to GoCodeAlone/rod for maintenance/dep control.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +56 to +66
type browserBackend struct {
opts BrowserOptions

// overrideHost replaces hoverHost for local tests. Empty means production
// (uses hoverHost). Never set in production code.
overrideHost string

// Live handles, set by Login and torn down by Close.
browser *rod.Browser
launcher *rodlauncher.Launcher
}
Comment on lines +68 to +70
func newBrowserBackend(opts BrowserOptions) *browserBackend {
return &browserBackend{opts: opts}
}
Comment on lines +92 to +104
func (b *browserBackend) Login(ctx context.Context, c *Client) error {
c.mu.Lock()
alreadyFresh := !c.loggedAt.IsZero() && time.Since(c.loggedAt) < sessionStaleAfter
c.mu.Unlock()
if alreadyFresh {
return nil
}

if b.opts.Timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, b.opts.Timeout)
defer cancel()
}
Comment on lines +111 to +118
} else {
// Explicit path provided: validate it exists (unless Download would
// handle it, but an explicit path means the operator chose a specific
// binary — honor it literally).
if _, err := os.Stat(b.opts.Path); err != nil && !b.opts.Download {
return fmt.Errorf("%w: %s: %v", ErrChromeUnavailable, b.opts.Path, err)
}
}
Comment thread README.md
Comment on lines +178 to +180
Browser unit tests in `pkg/hoverclient` launch real Chrome locally and may take
~20 s. They skip automatically when no Chrome binary is available and
`HOVER_BROWSER_DOWNLOAD` is not set.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants