[GH-ISSUE #2593] fix(browser): observation reports an old final URL after an excluded redirect #912

Closed
opened 2026-10-15 18:29:30 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 13, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2593

Summary

An observation starting on an approved page returns that page's old URL as its final URL when a main-frame redirect request leaves the approved origins. The window correctly stops and hides the destination, but the result presents a previous observation as the final location.

Runtime or environment

Source revision 8b181716ac, Node v26.10.0, npm 11.19.1, and locked dependencies. Verification used an in-memory CDP connection without launching a browser.

Steps to reproduce

Create src/browser/redirect-final-url.test.ts with this focused fixture, then run npm run test:focused -- src/browser/redirect-final-url.test.ts. This fixture injects the redirect at the next setup call after Network.enable. It reproduced the same failure as a separately executed fixture that injects the event once Network.enable completes.

import { expect, it } from "vitest";
import { observeCdpSession } from "./CdpObservationSession.js";
import type { CdpEvent } from "./CdpConnection.js";
it("represents a final URL as unknown after an excluded redirect", async () => {
  const listeners = new Set<(event: CdpEvent) => void>();
  const connection = {
    onEvent: (fn: (event: CdpEvent) => void) => { listeners.add(fn); return () => listeners.delete(fn); },
    onDisconnect: () => () => {},
    send: async (method: string) => {
      if (method === "Page.getFrameTree") return { frameTree: { frame: { id: "main", url: "https://app.example.test/start" } } };
      if (method === "Page.setLifecycleEventsEnabled") for (const fn of listeners) fn({ method: "Network.requestWillBeSent", params: { type: "Document", frameId: "main", redirectResponse: { status: 302 }, request: { url: "https://outside.example.test/end" } } });
      return {};
    },
  };
  const result = await observeCdpSession({
    connection: connection as never, sessionId: undefined,
    discovery: { version: { product: "Chrome/1", protocol_version: "1.3", revision: "1", user_agent: "ua", js_version: "1" } } as never,
    target: { id: "page", type: "page", title: "", url: "https://app.example.test/start", attached: null },
    input: { cdp_endpoint: "http://127.0.0.1:9222", allowed_origins: ["https://app.example.test"], target_id: "page", observation_ms: 100 },
  });
  expect(result.window.end_reason).toBe("target_left_scope");
  expect(result.target.final_url).toBeNull();
});

Expected behavior

When observation ends before it can establish an approved final destination, final_url is null. An excluded redirect must preserve the same unknown-value semantics as the other excluded navigation paths.

Actual behavior

The executed focused test returned end_reason target_left_scope and final_url https://app.example.test/start. The assertion requiring null failed. The redirect's excluded destination was hidden correctly.

Evidence

  • Expected source: AGENTS.md, Architecture and Cleanup, requires explicit unknowns; src/domain/browserSession.ts:44-50 defines the final URL result; src/browser/CdpObservationSession.ts:240-264 and 387-393 establish null for excluded or unknown destinations.
  • Failure source: src/browser/CdpObservationSession.ts:267-291 terminates on an excluded main-frame redirect without clearing the previous URL; the returned final_url at 114 retains it." Reason: The current source returns final_url at line 114, while line 123 returns completeness metadata
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The supported observation provider authorizes the initial target and scopes its origin before calling this session collector. The event matches the observation session, resource type, and main-frame ID, and includes a redirect response, so it passes the redirect guards. No later final probe runs after target_left_scope. No matching existing issue was found in read-only searches.

Restoration check

Repeat the excluded redirect case and require target_left_scope, a hidden redirect URL, and null final_url. As a control, an approved navigation commit or an elapsed window with an approved final frame-tree URL must retain that approved final URL.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded observation returns misleading final-location metadata for a supported redirect event. The executed test establishes this event path, not its frequency in real browsers.
  • Workaround status: available
  • Workaround basis: Consumers can treat final_url as unknown whenever end_reason is target_left_scope, without changing the target or weakening collection rules.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2593 ## Summary An observation starting on an approved page returns that page's old URL as its final URL when a main-frame redirect request leaves the approved origins. The window correctly stops and hides the destination, but the result presents a previous observation as the final location. ## Runtime or environment Source revision 8b181716aca2cb467be499cba226abfb5ae19141, Node v26.10.0, npm 11.19.1, and locked dependencies. Verification used an in-memory CDP connection without launching a browser. ## Steps to reproduce Create src/browser/redirect-final-url.test.ts with this focused fixture, then run npm run test:focused -- src/browser/redirect-final-url.test.ts. This fixture injects the redirect at the next setup call after Network.enable. It reproduced the same failure as a separately executed fixture that injects the event once Network.enable completes. ```ts import { expect, it } from "vitest"; import { observeCdpSession } from "./CdpObservationSession.js"; import type { CdpEvent } from "./CdpConnection.js"; it("represents a final URL as unknown after an excluded redirect", async () => { const listeners = new Set<(event: CdpEvent) => void>(); const connection = { onEvent: (fn: (event: CdpEvent) => void) => { listeners.add(fn); return () => listeners.delete(fn); }, onDisconnect: () => () => {}, send: async (method: string) => { if (method === "Page.getFrameTree") return { frameTree: { frame: { id: "main", url: "https://app.example.test/start" } } }; if (method === "Page.setLifecycleEventsEnabled") for (const fn of listeners) fn({ method: "Network.requestWillBeSent", params: { type: "Document", frameId: "main", redirectResponse: { status: 302 }, request: { url: "https://outside.example.test/end" } } }); return {}; }, }; const result = await observeCdpSession({ connection: connection as never, sessionId: undefined, discovery: { version: { product: "Chrome/1", protocol_version: "1.3", revision: "1", user_agent: "ua", js_version: "1" } } as never, target: { id: "page", type: "page", title: "", url: "https://app.example.test/start", attached: null }, input: { cdp_endpoint: "http://127.0.0.1:9222", allowed_origins: ["https://app.example.test"], target_id: "page", observation_ms: 100 }, }); expect(result.window.end_reason).toBe("target_left_scope"); expect(result.target.final_url).toBeNull(); }); ``` ## Expected behavior When observation ends before it can establish an approved final destination, final_url is null. An excluded redirect must preserve the same unknown-value semantics as the other excluded navigation paths. ## Actual behavior The executed focused test returned end_reason target_left_scope and final_url https://app.example.test/start. The assertion requiring null failed. The redirect's excluded destination was hidden correctly. ## Evidence - Expected source: AGENTS.md, Architecture and Cleanup, requires explicit unknowns; src/domain/browserSession.ts:44-50 defines the final URL result; src/browser/CdpObservationSession.ts:240-264 and 387-393 establish null for excluded or unknown destinations. - Failure source: src/browser/CdpObservationSession.ts:267-291 terminates on an excluded main-frame redirect without clearing the previous URL; the returned final_url at 114 retains it." Reason: The current source returns final_url at line 114, while line 123 returns completeness metadata - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The supported observation provider authorizes the initial target and scopes its origin before calling this session collector. The event matches the observation session, resource type, and main-frame ID, and includes a redirect response, so it passes the redirect guards. No later final probe runs after target_left_scope. No matching existing issue was found in read-only searches. ## Restoration check Repeat the excluded redirect case and require target_left_scope, a hidden redirect URL, and null final_url. As a control, an approved navigation commit or an elapsed window with an approved final frame-tree URL must retain that approved final URL. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A bounded observation returns misleading final-location metadata for a supported redirect event. The executed test establishes this event path, not its frequency in real browsers. - Workaround status: available - Workaround basis: Consumers can treat final_url as unknown whenever end_reason is target_left_scope, without changing the target or weakening collection rules.
bsakaguchi 2026-10-15 18:29:30 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#912
No description provided.