Files
rea/tests/boundary/browser/cdpBrowserProvider.document_script.test.ts
T
morluto b5e98915c6 fix(tools)!: simplify direct invocation and harden boundary contracts (#555)
Remove REA permission grants, scope ceilings, approval confirmations, and inert settings. Preserve selected local evidence, accept caller-chosen commands and paths, and report actual runtime failures.

Harden provider results, schema serialization, snapshot evidence binding, archive paths, source maps, native screenshots, managed EH parsing, and cancellation cleanup. Regenerate tool metadata and document direct invocation.

BREAKING CHANGE: Removed permission configuration and policy commands, approval fields, filesystem scope inputs, and fixed confirmation options. Process capture inherits the host environment and accepts command names; callers must refresh tool schemas and migrate removed fields.
2026-10-05 20:16:44 +08:00

418 lines
14 KiB
TypeScript

import { expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
import { analyzeWebBundleInputSchema } from "../../../src/domain/webBundleAnalysis.js";
import { discoverWebMcpToolsInputSchema } from "../../../src/domain/webMcpDiscovery.js";
import { captureWebScreenshotInputSchema } from "../../../src/domain/webScreenshot.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js";
describeBrowser("CdpBrowserProvider: document script 1", () => {
it("rejects unknown WebMCP discovery fields", () => {
expect(
discoverWebMcpToolsInputSchema.safeParse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://example.test"],
target_id: "target",
unknown_field: true,
}).success,
).toBe(false);
});
it("captures approved accessibility text inline", async () => {
const browser = await startFakeCdpBrowser();
trackBrowser(browser);
const request = inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
include_accessibility_text: true,
});
const result = await new CdpBrowserProvider().inspectPage(request);
if (!result.ok) throw result.error;
expect(result.value.accessibility).toMatchObject({
text_capture: {
status: "included",
retained_bytes: 27,
},
nodes: [expect.objectContaining({ name: "Submit report" })],
});
});
it("includes allowed script source only after explicit approval", async () => {
const browser = await startFakeCdpBrowser();
trackBrowser(browser);
const provider = new CdpBrowserProvider();
const result = await provider.inspectPage(
inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
include_script_sources: true,
}),
);
if (!result.ok) throw result.error;
expect(result.value.scripts.items[0]?.source).toEqual(
expect.objectContaining({
included: true,
artifact: expect.objectContaining({
text: "export const observed = 'source-secret';",
bytes: 40,
sha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
}),
}),
);
expect(browser.commands.map((command) => command.method)).toContain(
"Debugger.getScriptSource",
);
});
it("fetches and validates source maps only after separate approval", async () => {
const browser = await startFakeCdpBrowser({
sourceMapBody: JSON.stringify({
version: 3,
names: [],
sources: ["src/main.ts"],
sourcesContent: ["export const original = true;"],
mappings: "AAAA",
}),
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().analyzeBundle(
analyzeWebBundleInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
fetch_source_maps: true,
}),
);
if (!result.ok) throw result.error;
expect(result.value.observations.source_maps).toMatchObject({
status: "included",
items: [
{
status: "included",
original_sources: [
expect.objectContaining({
source: `${browser.allowedOrigin}/src/main.ts`,
}),
],
mappings: [expect.any(Object)],
},
],
});
const sourceMapRequest = browser.httpRequests.find(({ url }) =>
url.startsWith("/app.js.map"),
);
expect(sourceMapRequest).toEqual({
url: "/app.js.map?token=map-secret",
authorization: undefined,
cookie: undefined,
referer: undefined,
});
});
});
describeBrowser("CdpBrowserProvider: document script 2", () => {
it("discovers untrusted WebMCP declarations without registering or invoking them", async () => {
const browser = await startFakeCdpBrowser({ webMcpTools: true });
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value).toMatchObject({
status: "available",
tools: {
total: 1,
items: [
{
name: "search_orders",
description: "Search orders; authorization=Bearer tool-secret",
declaration_kind: "declarative",
owner_origin: browser.allowedOrigin,
annotations: {
read_only: true,
untrusted_content: true,
autosubmit: false,
},
trust: "page-declared-untrusted",
registration_source: {
url: `${browser.allowedOrigin}/app.js?token=tool-source-secret`,
line: 12,
column: 4,
},
},
],
},
});
const serialized = JSON.stringify(result.value);
expect(serialized).toContain("tool-secret");
expect(serialized).toContain("tool-source-secret");
for (const secret of ["schema-secret", "private-tool-secret"])
expect(serialized).not.toContain(secret);
const methods = browser.commands.map(({ method }) => method);
expect(methods).toContain("WebMCP.enable");
expect(methods).not.toContain("WebMCP.invokeTool");
expect(methods).not.toContain("Runtime.evaluate");
});
it("reports WebMCP unavailable when the experimental domain is absent", async () => {
const browser = await startFakeCdpBrowser({
unsupportedMethods: ["WebMCP.enable"],
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.status).toBe("unavailable");
expect(result.value.tools.items).toEqual([]);
expect(result.value.completeness.unavailable_sections).toContain(
"webmcp_tools",
);
});
it("rejects WebMCP evidence when the page leaves its approved origin", async () => {
const browser = await startFakeCdpBrowser({
webMcpTools: true,
frameUrlAfterFirstRead: "https://private.example.test/tools",
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
}),
);
expect(result).toMatchObject({
ok: false,
error: {
_tag: "BrowserObservationError",
operation: "discover_webmcp_tools",
reason: "target_not_allowed",
},
});
expect(browser.commands.map(({ method }) => method)).not.toContain(
"WebMCP.invokeTool",
);
});
it("returns every WebMCP registration in the replay", async () => {
const browser = await startFakeCdpBrowser({
webMcpTools: true,
extraCollections: true,
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.tools).toMatchObject({
total: 2,
items: [expect.any(Object), expect.any(Object)],
});
expect(result.value.completeness.truncated_sections).not.toContain(
"webmcp_tools",
);
});
it("retains registrations from authorized frames beyond the former frame cap", async () => {
const browser = await startFakeCdpBrowser({
webMcpTools: true,
webMcpFrameCount: 1_001,
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.tools.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "beyond_frame_limit_tool",
frame_id: "webmcp-frame-1000",
}),
]),
);
expect(result.value.completeness.truncated_sections).not.toContain(
"webmcp_tools",
);
});
});
describeBrowser("CdpBrowserProvider WebMCP inventory completeness", () => {
it("retains every field in a large declared WebMCP input schema", async () => {
const propertyCount = 5_001;
const browser = await startFakeCdpBrowser({
webMcpTools: true,
webMcpSchemaPropertyCount: propertyCount,
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
const shape = result.value.tools.items[0]?.input_schema_shape;
expect(
shape?.properties.filter(({ path }) =>
path.startsWith("/properties/field_"),
),
).toHaveLength(propertyCount * 2);
expect(shape?.properties).toContainEqual({
path: "/properties/field_5000/type",
types: ["string"],
observations: 1,
});
});
});
describeBrowser("CdpBrowserProvider: document script 3", () => {
it("removes WebMCP declarations after their child frame leaves scope", async () => {
const browser = await startFakeCdpBrowser({
webMcpTools: true,
extraCollections: true,
webMcpChildLeavesScope: true,
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
const names = result.value.tools.items.map(({ name }) => name);
expect(names).toEqual(
expect.arrayContaining(["search_orders", "update_order"]),
);
expect(names).not.toEqual(
expect.arrayContaining(["child_tool", "escaped_child_tool"]),
);
expect(result.value.completeness.policy_filtered_sections).toContain(
"webmcp_tools",
);
expect(JSON.stringify(result.value)).not.toContain(
"cross-origin-child-secret",
);
});
it("captures an explicitly approved content-addressed viewport screenshot", async () => {
const browser = await startFakeCdpBrowser();
trackBrowser(browser);
const result = await new CdpBrowserProvider().captureScreenshot(
captureWebScreenshotInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
}),
);
if (!result.ok) throw result.error;
expect(result.value).toMatchObject({
viewport: { width: 1, height: 1 },
artifact: {
sha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
bytes: 70,
media_type: "image/png",
},
});
expect(browser.commands.map(({ method }) => method)).toContain(
"Page.captureScreenshot",
);
});
it("discards screenshot pixels when the main frame navigates during capture", async () => {
const browser = await startFakeCdpBrowser({
navigateDuringScreenshotUrl: "https://private.example.test/screenshot",
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().captureScreenshot(
captureWebScreenshotInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
}),
);
expect(result).toMatchObject({
ok: false,
error: {
_tag: "BrowserObservationError",
operation: "capture_web_screenshot",
reason: "target_not_allowed",
},
});
});
});
describeBrowser("CdpBrowserProvider: document script 4", () => {
it("returns every frame, resource, worker, accessibility, and storage inventory item", async () => {
const browser = await startFakeCdpBrowser({ extraCollections: true });
trackBrowser(browser);
const request = inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
include_storage_keys: true,
});
const result = await new CdpBrowserProvider().inspectPage(request);
if (!result.ok) throw result.error;
expect(result.value.completeness.truncated_sections).toEqual([]);
expect(result.value.frames.length).toBeGreaterThan(1);
expect(result.value.resources.length).toBeGreaterThan(1);
expect(result.value.workers.length).toBeGreaterThan(1);
expect(result.value.accessibility).toMatchObject({ total_nodes: 4 });
expect(result.value.accessibility.nodes).toHaveLength(4);
expect(result.value.storage.local_storage_keys.length).toBeGreaterThan(1);
expect(result.value.storage.session_storage_keys.length).toBeGreaterThan(1);
expect(result.value.storage.indexed_db_names).toHaveLength(2);
expect(result.value.storage.cache_names).toHaveLength(2);
expect(result.value.storage.content_fingerprints).toEqual([]);
});
});