Skip to content

feat: initial scaffold for workflow-plugin-hover#1

Merged
intel352 merged 1 commit into
mainfrom
feat/initial-scaffold
May 20, 2026
Merged

feat: initial scaffold for workflow-plugin-hover#1
intel352 merged 1 commit into
mainfrom
feat/initial-scaffold

Conversation

@intel352
Copy link
Copy Markdown
Contributor

Hover DNS scaffold: TOTP (RFC 6238) + cookie-jar login client + plugin manifest. 9 tests pass.

Per workflow PR#735 SPEC T9..T13 (DNS providers plan, Hover slice).

Hover ships no official API. This scaffold mimics the browser-side
auth flow used by pjslauta/hover-dyn-dns:

internal/hover/totp.go — RFC 6238 TOTP (SHA-1, 30s step, 6 digits).
Pure Go; no external deps. ParseBase32 accepts the Hover 2FA-setup
seed format (whitespace/case-insensitive). Tested against RFC 6238
Appendix B vectors.

internal/hover/client.go — Cookie-jar-backed http client:
- GET /signin → parse CSRF _token (regex)
- POST /signin (username, password, _token)
- GET /signin/totp → parse fresh _token
- POST /signin/totp (code, _token)
- ListRecords / CreateRecord / UpdateRecord / DeleteRecord against
  /api/domains/<zone>/dns + /api/dns/<id>
- ensureLogin cache: re-auths after 1h idle.

plugin.json — declares resourceTypes [infra.dns], moduleTypes
[iac.provider.hover], required_secrets[]: HOVER_USERNAME,
HOVER_PASSWORD (sensitive), HOVER_TOTP_SECRET (sensitive).

internal/serve.go + cmd/workflow-plugin-hover/main.go — gRPC
entrypoint placeholder. Full IaC ResourceDriver registration lands
once workflow#640 Phase 3 stabilises.

Tests (9):
- TOTP: 4 vectors (RFC 6238 Appendix B canonical 20-byte secret) +
  parser edge cases (whitespace, case, too-short, bad alphabet).
- Client: two-step login hits expected paths in order; second
  ensureLogin within window is a cache hit; CSRF parse failure
  surfaces a clear error; NewClient refuses empty creds.

CI: go vet + go test -race.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@intel352 intel352 merged commit e07aba7 into main May 20, 2026
1 check passed
intel352 added a commit that referenced this pull request Jun 1, 2026
…— v0.5.0 (#30)

* docs(hover): headless-browser auth design + ADR 0001 (defeat Imperva 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>

* docs(hover): harden browser auth design review

* docs(hover): plan browser-backed auth implementation

* docs(hover): review browser auth implementation plan

* docs(hover): align browser auth plan with design

* chore: lock scope for hover browser auth

* test(hoverclient): add live browser auth viability gate

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>

* docs(hover): backport go-rod driver spike evidence into design

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>

* docs(hover): record 2026-05-30 Imperva-bypass re-check (go-rod unchanged)

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>

* fix(hoverclient): keep profile dir on local launcher (Kill, not Cleanup)

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>

* docs(hover): backport live-gate result — Imperva cleared, 2FA model

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>

* chore(hover): Go 1.26.3 + x/net v0.55.0 + gopls modernize

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>

* docs(hover): ADR 0002 fork go-rod -> GoCodeAlone/rod + design backport

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>

* feat(hoverclient): use GoCodeAlone/rod fork for the browser driver

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>

* docs(hover): backport production live proof (Imperva cleared, TOTP, 30 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>

* refactor(hoverclient): add browser backend configuration seam

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>

* feat(hoverclient): browser login mints Imperva clearance + hands cookies 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>

* feat(hoverclient): execute hover dns writes in-browser (hybrid write 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>

* docs(hover): browser-auth README + v0.5.0 manifests + live plugin test

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>

* docs(hover): security review + harden bot-challenge classification

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>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant