ADR 0024 — Browser Capture Extension
Context
Section titled “Context”Restura’s web build cannot capture arbitrary browser traffic or reach localhost (browser sandbox + CORS) — the same limitation Postman Interceptor fills for Postman. We want a capture extension that beats Interceptor on Restura’s home turf: multi-protocol capture (WebSocket / SSE / GraphQL / gRPC-web), secrets that never leak, and a clean path into Restura’s collection/AI workbench.
Two forces shape the design. First, the capture pipeline — normalize, classify, redact, export — is identical whether the consumer is the extension’s own export button or the desktop app; duplicating it would drift from Restura’s audited redaction (ADR 0007) and OpenCollection logic (ADR 0008). Second, MV3’s chrome.webRequest cannot see response bodies, so rich capture needs chrome.debugger (CDP).
Decision
Section titled “Decision”Approach C — one backend-agnostic core, thin adapters, mirroring the shared protocol layer.
-
shared/capture/holds the entire pipeline as pure, Zod-typed modules:cdp-normalizer(CDP Network/WebSocket/EventSource events →CapturedExchange),protocol-classifier,secret-extractor(redacts into{{name}}placeholders + reports secrets),to-har,to-opencollection.schema.tsis the source of truth — types arez.infer’d from it, and the samecaptureSessionSchemavalidates untrusted bridge payloads. The module never imports fromsrc/; it emits the OpenCollection document shape directly (schema-conformance is verified by a renderer-side test, not a shared→src import). -
extension/is a new MV3 subproject (Vite multi-entry —@crxjs/vite-pluginpredates Vite 8 — React + Zod). A service worker owns a single, per-tabchrome.debuggerattachment, streams events through the shared normalizer, redacts as exchanges complete, and persists the session tochrome.storage.sessionso an MV3 worker restart mid-capture loses nothing. Side panel + popup + options pages drive it. Two sinks: standalone OpenCollection/HAR file export (no Restura required) and Send to Desktop. -
electron/main/handlers/capture-bridge-handler.tsis a 127.0.0.1-only HTTP receiver. Pure auth/origin logic lives incapture-bridge-protocol.ts(separately unit-tested, electron-free). It converts received sessions via the shared core and pushes an OpenCollection doc to the renderer (EVENT.captureReceived). Gated by capabilitycapture.desktopBridge. -
Renderer wiring. The preload exposes a
capturesurface (start/stop/status bridge +onReceived), type-checked againstElectronAPI.CaptureImportListener(mounted app-level) shows a confirmation dialog before importing a pushed session as a collection — never a silent import, since any local process holding the bridge token could push a document.CaptureBridgeCard(Settings → Data) starts/stops the bridge and shows the one-time<port>:<token>pairing code the user pastes into the extension.
Targets for Phase 1 are Desktop + Standalone only; web-app messaging and a CORS bridge for the web SPA are deferred.
Security
Section titled “Security”chrome.debuggerattaches only to the user-selected tab and detaches on stop / tab close / worker teardown.- Redaction precedes every persistence/export/transmit.
secret-extractorstrips theCREDENTIAL_HEADER_NAMESdenylist +x-*-(token|key|secret)+ JWT/Bearer/key=val/prefixed-provider-token body patterns. Completeness is a security regression test (tests/security/capture-redaction.test.ts). - Bridge hardening: loopback bind, per-pairing bearer token,
Origin/Hostloopback validation (DNS-rebind / CSRF defence — a malicious page can POST to 127.0.0.1 but carries a remoteOrigin), Zod payload validation, body-size cap. Token is shown to the trusted renderer only, never over the HTTP surface. - Pairing is user-initiated: desktop surfaces a
<port>:<token>code the user pastes into the extension options page. The extension cannot read the desktop handshake file directly.
Consequences
Section titled “Consequences”- The capture logic is testable without a browser (unit tests over recorded CDP-event fixtures), and the extension/desktop share exactly one redaction path.
- Live
chrome.debuggercapture is not end-to-end tested: Playwright is a CDP client and a second debugger attach on the same target conflicts. The e2e (e2e/extension-capture.spec.ts) loads the real bundle in new-headless Chromium and exercises the UI → storage → shared-export path; the CDP path is covered by normalizer fixtures. @crxjs/vite-pluginis avoided until it supports Vite 8; the plain multi-entry build is the tradeoff (no extension HMR, but version-proof).
Future (Phases 2–3)
Section titled “Future (Phases 2–3)”OpenAPI 3.1 inference and smart endpoint sessionization; AI-Lab eval-dataset generation, mock-route generation, and contract-drift diffing from captured traffic.
Related
Section titled “Related”- ADR 0001 — Shared protocol layer — the one-core / thin-adapters pattern this mirrors.
- ADR 0007 — SecretRef pattern — the redaction posture the capture core inherits.
- ADR 0008 — OpenCollection native format — the export target.
- ADR 0025 — VS Code extension — the sibling editor integration.
- End-to-end tests — the Playwright harness
e2e/extension-capture.spec.tsruns the UI → storage → shared-export path in.