From 8481be9c9709b13f572fefefcf8508d9e2605386 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 14 Jul 2026 05:12:40 +0000 Subject: [PATCH] fix(browser): drop disallowed redirect evidence --- src/browser/CdpCaptureEvents.ts | 17 ++++++++++------- tests/cdpBrowserProvider.test.ts | 19 +++++++++++++++++++ tests/fixtures/fakeCdpBrowser.ts | 14 ++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/browser/CdpCaptureEvents.ts b/src/browser/CdpCaptureEvents.ts index 83da575d8..65560173e 100644 --- a/src/browser/CdpCaptureEvents.ts +++ b/src/browser/CdpCaptureEvents.ts @@ -139,13 +139,11 @@ export class CdpCaptureEvents { const requestId = stringValue(params.requestId); const request = recordValue(params.request); const sanitized = allowedSanitizedUrl(request?.url, this.allowedOrigins); - if ( - requestId === undefined || - requestId.length > 256 || - request === undefined || - sanitized === undefined - ) + if (requestId === undefined || requestId.length > 256) return; + if (request === undefined || sanitized === undefined) { + this.network.delete(requestId); return; + } if ( this.network.size >= this.input.limits.max_network_events && !this.network.has(requestId) @@ -181,7 +179,12 @@ export class CdpCaptureEvents { if (requestId === undefined || requestId.length > 256) return; const current = this.network.get(requestId); const response = recordValue(params.response); - if (current === undefined || response === undefined) return; + const sanitized = allowedSanitizedUrl(response?.url, this.allowedOrigins); + if (current === undefined) return; + if (response === undefined || sanitized === undefined) { + this.network.delete(requestId); + return; + } this.network.set(requestId, { ...current, status: numberValue(response.status) ?? null, diff --git a/tests/cdpBrowserProvider.test.ts b/tests/cdpBrowserProvider.test.ts index a2b4649a9..4081e6fb6 100644 --- a/tests/cdpBrowserProvider.test.ts +++ b/tests/cdpBrowserProvider.test.ts @@ -127,6 +127,25 @@ describe("CdpBrowserProvider", () => { expect(methods).not.toContain("Runtime.evaluate"); }); + it("drops network evidence when a request redirects outside the approved origin", async () => { + const browser = await startFakeCdpBrowser({ + redirectToDisallowedOrigin: true, + }); + browsers.push(browser); + const result = await new CdpBrowserProvider().inspectPage( + inspectWebPageInputSchema.parse({ + cdp_endpoint: browser.endpoint, + allowed_origins: [browser.allowedOrigin], + target_id: "allowed-page", + approved: true, + observation_ms: 0, + }), + ); + + if (!result.ok) throw result.error; + expect(result.value.network.requests).toEqual([]); + }); + it("includes allowed script source only after explicit approval", async () => { const browser = await startFakeCdpBrowser(); browsers.push(browser); diff --git a/tests/fixtures/fakeCdpBrowser.ts b/tests/fixtures/fakeCdpBrowser.ts index 0b3b43a94..22fae3192 100644 --- a/tests/fixtures/fakeCdpBrowser.ts +++ b/tests/fixtures/fakeCdpBrowser.ts @@ -33,6 +33,7 @@ interface FakeOptions { readonly navigateDuringCaptureUrl?: string; readonly extraCollections?: boolean; readonly foreignSessionEvents?: boolean; + readonly redirectToDisallowedOrigin?: boolean; } /** Start a real HTTP/WebSocket fake at the same seams as a user-owned browser. */ @@ -487,9 +488,22 @@ const emitEvents = ( ], }, }); + if (options.redirectToDisallowedOrigin === true) + event(socket, "Network.requestWillBeSent", command.sessionId, { + requestId: "request-1", + type: "Fetch", + request: { + url: "https://private.example.test/redirected", + method: "GET", + }, + }); event(socket, "Network.responseReceived", command.sessionId, { requestId: "request-1", response: { + url: + options.redirectToDisallowedOrigin === true + ? "https://private.example.test/redirected" + : url, status: 200, mimeType: "application/json", headers: { "Set-Cookie": "response-secret" },