You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document is the program-level overview for the stacked ports-and-adapters refactor.
Goals
The HTTP/2 program needs Cypress to intercept and mutate network traffic without routing every request through the MITM proxy. Today, intercept logic, document injection, cookie simulation, and config policies are embedded in proxy and net-stubbing middleware.
This refactor applies a hexagonal architecture (aka, "ports and adapters) to network interception:
Goal
How
Swap transport without rewriting intercept logic
Pure NetworkInterceptionCore orchestrates matching, subscriptions, and policy phases; infrastructure lives behind driven ports
Keep cy.intercept stable
Driver→server IPC unchanged at the boundary; ForInterceptRegistration adapter wraps legacy handlers
Configurator policies as data
blockHosts, CSP allow-list, document rewrite register as NetworkPolicy objects, evaluated by NetworkPolicyRegistry
Incremental delivery
8 stacked PRs, each behavior-neutral, each mergeable independently
HTTP/2 readiness
Explicit bypass at forwardToOrigin; stub ForBrowserNetworkAutomation for future CDP/BiDi adapters
Non-goals for this stack: changing user-facing APIs, replacing the proxy entirely, or implementing the HTTP/2 browser-automation path (that is a follow-on epic using the same core).
Architecture at a glance
Before (monolith)
Intercept matching, handler merge, outbound forwarding, HTML injection, cookie jar updates, and command-log emission all lived as inline logic inside @packages/proxy and @packages/net-stubbing middleware. Types were owned by net-stubbing. Server constructed NetworkProxy directly.
flowchart TB
subgraph driving [Driving ports]
FIR[ForInterceptRegistration]
FNPR[ForNetworkPolicyRegistration]
end
subgraph driverPkg ["@packages/driver"]
DCL[DriverCommandLogAdapter]
CY[cy.intercept]
end
subgraph serverPkg ["@packages/server"]
CPR[createProxyRuntime]
CNPA[ConfiguratorNetworkPolicyAdapter]
RDP[registerDefaultNetworkPolicies]
CDNIC[createDefaultNetworkInterceptionCore]
end
subgraph corePkg ["@packages/network-interception"]
NIC[NetworkInterceptionCore]
NPR[NetworkPolicyRegistry]
Pure[pure modules]
end
subgraph proxyPkg ["@packages/proxy/adapters"]
PRI[ProxyRequestInterceptionAdapter]
PRS[ProxyResponseInterceptionAdapter]
PDP[ProxyDocumentPreparationAdapter]
PNC[ProxyNetworkCaptureAdapter]
PCS[ProxyCookieStateAdapter]
PCL[ProxyCommandLogAdapter]
end
subgraph netstubPkg ["@packages/net-stubbing"]
DIRA[DriverInterceptRegistrationAdapter]
HIR[handleInterceptRequest/Response]
end
subgraph mw [Proxy middleware]
REQ[request-middleware]
RES[response-middleware]
end
CY -->|socket| FIR
FIR --> DIRA --> HIR
CPR --> CNPA --> NPR
CPR --> RDP
CPR --> CDNIC --> NIC
CDNIC --> PRI & PRS & PDP & PNC & PCS & PCL
CNPA -->|policyRegistration| NIC
REQ & RES --> NIC
NIC --> Pure
NIC --> PRI & PRS & PDP & PNC & PCS & PCL
DCL -.->|logInterception| FCL[ForCommandLog]
PCL --> FCL
Loading
How HTTP/2 fits in
Two transport paths, one core
The refactor deliberately separates what happens to a request (match routes, run policies, fulfill, log) from how bytes move (MITM proxy vs browser network API).
Key boundary:ForRequestInterception.forwardToOrigin sends the proxied request to the origin via Node HTTP. It is documented as the HTTP/2 bypass boundary — the browser-automation path must not call it; requests terminate at CDP Fetch instead of MITM forwarding.
On the HTTP/2 path, steps after correlation move into ForBrowserNetworkAutomation — the core's match/fulfill/policy logic stays, but forwardToOrigin drops out of the chain.
Stacked PR roadmap
Eight behavior-neutral PRs. Merge in order; each targets the previous branch as base.
Network interception: ports & adapters refactor
This document is the program-level overview for the stacked ports-and-adapters refactor.
Goals
The HTTP/2 program needs Cypress to intercept and mutate network traffic without routing every request through the MITM proxy. Today, intercept logic, document injection, cookie simulation, and config policies are embedded in proxy and net-stubbing middleware.
This refactor applies a hexagonal architecture (aka, "ports and adapters) to network interception:
NetworkInterceptionCoreorchestrates matching, subscriptions, and policy phases; infrastructure lives behind driven portscy.interceptstableForInterceptRegistrationadapter wraps legacy handlersblockHosts, CSP allow-list, document rewrite register asNetworkPolicyobjects, evaluated byNetworkPolicyRegistryforwardToOrigin; stubForBrowserNetworkAutomationfor future CDP/BiDi adaptersNon-goals for this stack: changing user-facing APIs, replacing the proxy entirely, or implementing the HTTP/2 browser-automation path (that is a follow-on epic using the same core).
Architecture at a glance
Before (monolith)
Intercept matching, handler merge, outbound forwarding, HTML injection, cookie jar updates, and command-log emission all lived as inline logic inside
@packages/proxyand@packages/net-stubbingmiddleware. Types were owned by net-stubbing. Server constructedNetworkProxydirectly.After (stage 7 — target)
Entry point:
packages/server/lib/network-runtime.tsDomain package:
packages/network-interception/(in early PRs, this ispackages/network-policy)Proxy adapters:
packages/proxy/lib/adapters/Port model
Driving ports (call into the core)
Who initiates work from outside the domain.
ForInterceptRegistrationDriverInterceptRegistrationAdapter(net-stubbing)cy.interceptroute add, subscribe, handler round-tripsForNetworkPolicyRegistrationConfiguratorNetworkPolicyAdapter(server)Driven ports (core calls out to infrastructure)
Who performs I/O, browser hooks, and side effects.
ForRequestInterceptionProxyRequestInterceptionAdapterForResponseInterceptionProxyResponseInterceptionAdapterForDocumentPreparationProxyDocumentPreparationAdapterForNetworkCaptureProxyNetworkCaptureAdapterForCookieStateProxyCookieStateAdapterForCommandLogProxyCommandLogAdapter(server),DriverCommandLogAdapter(driver)ForBrowserNetworkAutomationCore orchestrator
NetworkInterceptionCoreholds pure logic (route matching, subscription planning, handler merge, injection-level decisions, request-logging filter) and delegates I/O to injected driven ports. Middleware never calls adapters directly — it callsthis.networkInterceptionCore.*.System context (C4)
C4Container title Monorepo containers — refactor boundary Person(tester, "Test author") Container_Boundary(cypress, "Cypress") { Container(driver, "@packages/driver", "cy.* commands") Container(server, "@packages/server", "HTTP server, sockets, composition root") Container(proxy, "@packages/proxy", "MITM proxy middleware") Container(netstub, "@packages/net-stubbing", "Intercept middleware + driver adapter") Container(ni, "@packages/network-interception", "Ports, core, policies, types") Container(socket, "@packages/socket", "Driver-server IPC") Container(rewriter, "@packages/rewriter", "HTML/JS source rewrite") } System_Ext(origin, "Origin servers") Rel(tester, driver, "Tests") Rel(driver, socket, "net events") Rel(socket, server, "WebSocket") Rel(server, proxy, "handleHttpRequest") Rel(proxy, netstub, "middleware stack") Rel(proxy, origin, "forwardToOrigin today") Rel(netstub, ni, "core + types") Rel(server, ni, "composition root") Rel(proxy, ni, "delegates through core") Rel(proxy, rewriter, "document injection")Port / adapter topology (complete)
flowchart TB subgraph driving [Driving ports] FIR[ForInterceptRegistration] FNPR[ForNetworkPolicyRegistration] end subgraph driverPkg ["@packages/driver"] DCL[DriverCommandLogAdapter] CY[cy.intercept] end subgraph serverPkg ["@packages/server"] CPR[createProxyRuntime] CNPA[ConfiguratorNetworkPolicyAdapter] RDP[registerDefaultNetworkPolicies] CDNIC[createDefaultNetworkInterceptionCore] end subgraph corePkg ["@packages/network-interception"] NIC[NetworkInterceptionCore] NPR[NetworkPolicyRegistry] Pure[pure modules] end subgraph proxyPkg ["@packages/proxy/adapters"] PRI[ProxyRequestInterceptionAdapter] PRS[ProxyResponseInterceptionAdapter] PDP[ProxyDocumentPreparationAdapter] PNC[ProxyNetworkCaptureAdapter] PCS[ProxyCookieStateAdapter] PCL[ProxyCommandLogAdapter] end subgraph netstubPkg ["@packages/net-stubbing"] DIRA[DriverInterceptRegistrationAdapter] HIR[handleInterceptRequest/Response] end subgraph mw [Proxy middleware] REQ[request-middleware] RES[response-middleware] end CY -->|socket| FIR FIR --> DIRA --> HIR CPR --> CNPA --> NPR CPR --> RDP CPR --> CDNIC --> NIC CDNIC --> PRI & PRS & PDP & PNC & PCS & PCL CNPA -->|policyRegistration| NIC REQ & RES --> NIC NIC --> Pure NIC --> PRI & PRS & PDP & PNC & PCS & PCL DCL -.->|logInterception| FCL[ForCommandLog] PCL --> FCLHow HTTP/2 fits in
Two transport paths, one core
The refactor deliberately separates what happens to a request (match routes, run policies, fulfill, log) from how bytes move (MITM proxy vs browser network API).
flowchart TB subgraph today ["Today — proxy-default path (this stack)"] B1[Browser] --> P1[Cypress MITM proxy] P1 --> CORR[correlateBrowserPreRequest] CORR --> CORE1[NetworkInterceptionCore] CORE1 --> FWD[forwardToOrigin — Node HTTP] FWD --> O1[Origin] end subgraph future ["Future — HTTP/2 browser-automation path"] B2[Browser] --> CDP[CDP Fetch / BiDi network] CDP --> BNA[ForBrowserNetworkAutomation adapter] BNA --> CORE2[NetworkInterceptionCore — same core] CORE2 --> O2[Origin via browser stack] endKey boundary:
ForRequestInterception.forwardToOriginsends the proxied request to the origin via Node HTTP. It is documented as the HTTP/2 bypass boundary — the browser-automation path must not call it; requests terminate at CDP Fetch instead of MITM forwarding.Relevant code:
packages/network-interception/lib/ports/driven-ports.ts:forwardToOriginJSDocpackages/proxy/lib/adapters/send-request-outgoing.ts: Node HTTP outbound implementationpackages/network-interception/lib/runtime.ts— "Browser adapters swap in behind the same core in the HTTP/2 program"What stays the same across paths
NetworkInterceptionCoreblockHosts, CSP, rewrite)NetworkPolicyRegistrycy.interceptregistrationForInterceptRegistrationForCommandLogcorrelateBrowserPreRequestforwardToOriginForDocumentPreparationHTTP/2 epic placeholders (not in this stack)
ForBrowserNetworkAutomationcreateStubDrivenPorts()NetworkInterceptionRuntimeBrowser automation today (feeds the proxy path)
Even on the current proxy-default path, CDP/BiDi automation is involved upstream of the proxy:
Network.requestWillBeSentor BiDinetwork.beforeRequestSent).correlateBrowserPreRequest).forwardToOrigin.On the HTTP/2 path, steps after correlation move into
ForBrowserNetworkAutomation— the core's match/fulfill/policy logic stays, butforwardToOrigindrops out of the chain.Stacked PR roadmap
Eight behavior-neutral PRs. Merge in order; each targets the previous branch as base.
@packages/network-policyscaffold, types move,createProxyRuntimeForInterceptRegistration+ socket wiringBlockedHostsregistrationNetworkPolicyCore+ intercept middleware delegation@packages/network-interception, consolidate composition, wireBlockedHostsenforcementRequest lifecycle (proxy-default, post stage 7)
sequenceDiagram participant MW as Proxy middleware participant Core as NetworkInterceptionCore participant Pol as NetworkPolicyRegistry participant PRI as ProxyRequestInterceptionAdapter participant NS as net-stubbing I/O participant Origin MW->>Core: endRequestIfBlocked Core->>Pol: runPolicies request phase alt BlockedHosts matches Pol-->>MW: ctx.end 503 else not blocked MW->>Core: correlateBrowserPreRequest Core->>PRI: correlate MW->>Core: handleRequest Core->>NS: handleInterceptRequest MW->>Core: forwardToOrigin Core->>PRI: sendRequestOutgoing PRI->>Origin: Node HTTP Origin-->>MW: response stream MW->>Core: interceptResponse Core->>NS: handleInterceptResponse MW->>Core: setInjectionLevel / injectHtml / ... endPackage dependency rules
Verification
After stage 7 merges:
git diff refactor/ports-adapters-7 refactor/ports-adapters-backup # expect empty (plus any CI-only commits on branch 0)Full test gate: