Files
rea/src/domain/webBundleAnalyzer.test.ts

2186 lines
70 KiB
TypeScript

import {
projectAnalysisView,
analysisViewRequestSchema,
} from "./analysisView/analysisView.js";
import { describe, expect, it } from "vitest";
import { createWebPageInspection } from "../../tests/support/browser/webPageInspection.js";
import { analyzeJavaScriptStaticSource } from "./javascript/javascriptStaticAnalysis.js";
import { analyzeCapturedWebScript } from "./webBundleAnalyzer.js";
import { analyzeWebBundleFixture } from "../../tests/support/browser/analyzeWebBundleFixture.js";
import { buildCapturedWebBundleAnalysis } from "./webBundleAnalyzerResult.js";
import { webBundleAnalysisSchema } from "./webBundleAnalysis.js";
import { isIncludedScript } from "./webBundleAnalyzerState.js";
import { webBundleScriptAnalysisSchema } from "./webBundleScriptAnalysis.js";
import { createWebTextArtifact } from "./webContentArtifact.js";
const origin = "https://app.example.test";
it("shares runtime module declarations with source analysis while retaining inline type side effects", () => {
const source = `
import type {T} from "./erased-import";
export type {T} from "./erased-export";
export type * from "./erased-all";
import {type X} from "./inline-import";
export {type Y} from "./inline-export";
import "";
export * from "./runtime";
export const local = 1;
`;
const expected = ["./inline-import", "./inline-export", "", "./runtime"];
const result = analyzeWebBundleFixture(inspection(source));
expect(result.completeness.parse_failures).toBe(0);
expect(
result.observations.chunks.edges.map(({ specifier }) => specifier),
).toEqual(expected);
expect(
analyzeJavaScriptStaticSource(source)
.references.filter(({ kind }) => kind === "static-import")
.map(({ specifier }) => specifier),
).toEqual(expected);
});
it("retains standalone GraphQL Source operations in captured scripts", () => {
const result = analyzeWebBundleFixture(
inspection(`
const source = {
body: "query GetUser { user { id } }",
locationOffset: { line: 1, column: 1 },
};
`),
);
expect(result.observations.endpoints).toEqual([
expect.objectContaining({ value: "GetUser", mechanism: "graphql:query" }),
]);
});
it.each([
{
kind: "recovered",
source: "function legacy(a, a) { return a; } fetch('/partial');",
parsed: 2,
failures: 0,
reason: "recovered",
endpoints: ["/partial", "/complete"],
},
{
kind: "failed",
source: "function broken( {",
parsed: 1,
failures: 1,
reason: "could not be parsed",
endpoints: ["/complete"],
},
])(
"attributes $kind parse uncertainty only to its affected source",
({ source, parsed, failures, reason, endpoints }) => {
const captured = inspection(source);
const good = inspection("fetch('/complete');").scripts.items[0];
if (good === undefined) throw new Error("Missing valid script fixture");
captured.scripts.items.push({ ...good, script_key: "valid-script" });
captured.scripts.total += 1;
const result = analyzeWebBundleFixture(captured);
expect(result.completeness).toMatchObject({
status: "partial",
parsed_scripts: parsed,
parse_failures: failures,
});
expect(result.unknowns).toContainEqual({
dimension: "javascript_ast",
reason: expect.stringContaining(reason),
affected_script_keys: [captured.scripts.items[0]?.script_key],
});
expect(result.observations.endpoints.map(({ value }) => value)).toEqual(
endpoints,
);
},
);
it.each([
[
"const records = new Map(); const alias = records; alias.get('/accounts');",
false,
],
["new Map().delete('/accounts');", false],
[
"const records = new Map(); records.get('/accounts'); records.delete('/accounts');",
false,
],
[
"const records = new Map(); records.get = fetch; records.get('/accounts');",
true,
],
[
"Object.getPrototypeOf = () => Map.prototype; Object.getPrototypeOf({}).get = fetch; new Map().get('/accounts');",
true,
],
["client.get('/accounts');", true],
["eval('Map.prototype.get = fetch'); new Map().get('/accounts');", true],
] as const)(
"shares Map receiver certainty with application analysis: %s",
(source, candidate) => {
const input = source + " fetch('/network');";
const result = analyzeWebBundleFixture(inspection(input));
expect(result.observations.endpoints.map(({ value }) => value)).toEqual([
"/network",
]);
expect(
result.inferences
.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value),
).toEqual(candidate ? ["/accounts"] : []);
const staticResult = analyzeJavaScriptStaticSource(input);
expect(
staticResult.endpoints
.map(({ value, interpretation }) => [value, interpretation ?? null])
.sort(),
).toEqual(
candidate
? [
["/accounts", "candidate"],
["/network", null],
]
: [["/network", null]],
);
},
);
it("discloses widened returned-reference effects only for the affected script", () => {
const captured = inspection(`
function expose() { return Map.prototype; }
let cursor = { target: expose };
function walk(flag) {
while (flag) cursor = cursor.next;
patch(cursor.target());
}
new Map().get('/candidate');
fetch('/preserved');
`);
const independent = inspection(
"new Map().get('/intrinsic'); fetch('/independent');",
).scripts.items[0];
if (independent === undefined) throw new Error("Missing independent script");
captured.scripts.items.push({ ...independent, script_key: "independent" });
captured.scripts.total += 1;
const result = analyzeWebBundleFixture(captured);
expect(result.completeness).toMatchObject({
status: "partial",
parsed_scripts: 2,
parse_failures: 0,
});
expect(result.unknowns).toContainEqual({
dimension: "javascript_ast",
reason: expect.stringContaining("returned-reference projections"),
affected_script_keys: [captured.scripts.items[0]?.script_key],
});
expect(result.observations.endpoints.map(({ value }) => value)).toEqual([
"/preserved",
"/independent",
]);
expect(
result.inferences
.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value),
).toEqual(["/candidate"]);
});
it.each([
{
label: "inline declaration and schema spreads",
source: `document.modelContext.registerTool({
...{
name: "spread-tool",
description: "Copied from a literal",
inputSchema: {
...{
type: "object",
properties: { query: { type: "string" } }
}
}
}
});`,
name: "spread-tool",
description: "Copied from a literal",
properties: ["query"],
coverage: "complete",
},
{
label: "unrelated trailing literal spread",
source: `document.modelContext.registerTool({
name: "direct-tool",
description: "Explicit metadata",
inputSchema: { properties: { query: { type: "string" } } },
...{ timeout: 5 }
});`,
name: "direct-tool",
description: "Explicit metadata",
properties: ["query"],
coverage: "complete",
},
{
label: "null spread after explicit metadata",
source: `document.modelContext.registerTool({
name: "null-safe-tool",
description: "Null spread is inert",
inputSchema: { properties: { query: { type: "string" } } },
...null
});`,
name: "null-safe-tool",
description: "Null spread is inert",
properties: ["query"],
coverage: "complete",
},
{
label: "snake-case schema property from a spread",
source: `document.modelContext.registerTool({
...{
name: "snake-tool",
description: "Snake-case schema",
input_schema: { properties: { item_id: { type: "string" } } }
}
});`,
name: "snake-tool",
description: "Snake-case schema",
properties: ["item_id"],
coverage: "complete",
},
])(
"preserves WebMCP facts through $label",
({ source, name, description, properties, coverage }) => {
const result = analyzeWebBundleFixture(inspection(source));
expect(result.observations.webmcp_declarations).toEqual([
expect.objectContaining({
name,
description,
schema_property_names: properties,
schema_property_names_coverage: coverage,
trust: "page-declared-untrusted",
}),
]);
expect(result.completeness.status).toBe("complete");
},
);
it.each([
{
label: "unknown spread after explicit facts",
source: `document.modelContext.registerTool({
name: "possibly-overridden",
description: "Possibly overridden",
inputSchema: { properties: { query: { type: "string" } } },
...dynamicOptions
});`,
name: null,
description: null,
properties: [],
coverage: "unknown",
},
{
label: "explicit facts after an unknown spread",
source: `document.modelContext.registerTool({
...dynamicOptions,
name: "known-after-spread",
description: "Explicitly restored",
inputSchema: { properties: { query: { type: "string" } } }
});`,
name: "known-after-spread",
description: "Explicitly restored",
properties: ["query"],
coverage: "complete",
},
])(
"preserves rightmost uncertainty for $label",
({ source, name, description, properties, coverage }) => {
const result = analyzeWebBundleFixture(inspection(source));
expect(result.observations.webmcp_declarations).toEqual([
expect.objectContaining({
name,
description,
schema_property_names: properties,
schema_property_names_coverage: coverage,
trust: "page-declared-untrusted",
}),
]);
expect(result.completeness.status).toBe("complete");
},
);
it.each([
{
label: "an unknown camel-case schema override",
source: `document.modelContext.registerTool({
...dynamicOptions,
inputSchema: { properties: { camel: {} } },
...anotherDynamicOptions,
input_schema: { properties: { snake: {} } },
name: "tool",
description: "Explicit metadata"
});`,
name: "tool",
properties: [],
coverage: "unknown",
},
{
label: "an explicit null camel-case schema",
source: `document.modelContext.registerTool({
name: "null-alias",
description: "Fallback applies",
inputSchema: null,
input_schema: { properties: { snake: {} } }
});`,
name: "null-alias",
properties: ["snake"],
coverage: "complete",
},
{
label: "literal property spreads",
source: `document.modelContext.registerTool({
name: "spread-properties",
inputSchema: { properties: { ...{ query: {} }, direct: {} } }
});`,
name: "spread-properties",
properties: ["direct", "query"],
coverage: "complete",
},
{
label: "prototype setters are not schema properties",
source: `document.modelContext.registerTool({
name: "prototype-properties",
inputSchema: { properties: { __proto__: { hidden: {} }, direct: {}, ["__proto__"]: {} } }
});`,
name: "prototype-properties",
properties: ["__proto__", "direct"],
coverage: "complete",
},
{
label: "own schema properties before a prototype setter",
source: `document.modelContext.registerTool({
name: "own-before-prototype",
inputSchema: { properties: { known: {} }, __proto__: { properties: { inherited: {} } } }
});`,
name: "own-before-prototype",
properties: ["known"],
coverage: "complete",
},
{
label: "own schema properties after a prototype setter",
source: `document.modelContext.registerTool({
name: "own-after-prototype",
inputSchema: { __proto__: { properties: { inherited: {} } }, properties: { known: {} } }
});`,
name: "own-after-prototype",
properties: ["known"],
coverage: "complete",
},
{
label: "an unresolved property spread",
source: `document.modelContext.registerTool({
name: "unknown-properties",
inputSchema: { properties: { ...dynamicProperties, direct: {} } }
});`,
name: "unknown-properties",
properties: ["direct"],
coverage: "partial",
},
{
label: "an unresolved computed property key",
source: `document.modelContext.registerTool({
name: "computed-properties",
inputSchema: { properties: { [propertyName]: {}, direct: {} } }
});`,
name: "computed-properties",
properties: ["direct"],
coverage: "partial",
},
])(
"preserves WebMCP schema ownership for $label",
({ source, name, properties, coverage }) => {
const result = analyzeWebBundleFixture(inspection(source));
expect(result.observations.webmcp_declarations).toEqual([
expect.objectContaining({
name,
schema_property_names: properties,
schema_property_names_coverage: coverage,
trust: "page-declared-untrusted",
}),
]);
},
);
describe("web bundle analyzer", () => {
it("extracts graph, route, endpoint, vendor, and WebMCP evidence", () => {
const source = `
import { createApp } from "./chunk.js?token=secret";
const lazy = import("./lazy.js");
const routes = [{ path: "/users/:id?token=secret" }];
fetch("/api/users?authorization=secret");
document.modelContext.registerTool({
name: "lookup-user",
description: "Untrusted page declaration",
inputSchema: {
type: "object",
properties: {
userId: { type: "string" },
verbose: { type: "boolean" }
}
}
});
const __webpack_require__ = () => createApp(routes, lazy);
`;
const result = analyzeWebBundleFixture(inspection(source));
expect(result.observations.chunks.edges).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "static_import",
specifier: "./chunk.js?token=secret",
resolved_url: `${origin}/assets/chunk.js?token=secret`,
}),
expect.objectContaining({
kind: "dynamic_import",
specifier: "./lazy.js",
}),
]),
);
expect(result.observations.routes).toEqual([
expect.objectContaining({ value: "/users/:id?token=secret" }),
]);
expect(result.observations.endpoints).toEqual([
expect.objectContaining({
value: "/api/users?authorization=secret",
}),
]);
expect(result.observations.webmcp_declarations).toEqual([
expect.objectContaining({
name: "lookup-user",
trust: "page-declared-untrusted",
schema_property_names: ["userId", "verbose"],
schema_property_names_coverage: "complete",
}),
]);
expect(result.inferences).toEqual(
expect.arrayContaining([
expect.objectContaining({ value: "webpack", confidence: "high" }),
expect.objectContaining({ value: "Vue" }),
]),
);
expect(JSON.stringify(result)).toContain("authorization=secret");
expect(result.completeness.status).toBe("complete");
});
it("reports parser gaps without imposing a finding cap", () => {
const malformed = inspection("function broken( {");
const failed = analyzeWebBundleFixture(malformed);
expect(failed.completeness).toMatchObject({
status: "partial",
parse_failures: 1,
});
expect(failed.unknowns[0]?.dimension).toBe("javascript_ast");
const analyzed = analyzeWebBundleFixture(
inspection("fetch('/one'); fetch('/two'); fetch('/three');"),
);
expect(analyzed.observations.endpoints).toHaveLength(3);
expect(analyzed.completeness.status).toBe("complete");
});
it("analyzes AST nodes after the former fixed node ceiling", () => {
const source = `${";".repeat(250_001)}fetch('/last');`;
const result = analyzeWebBundleFixture(inspection(source));
expect(result.observations.endpoints).toContainEqual(
expect.objectContaining({ value: "/last" }),
);
expect(result.completeness.visited_ast_nodes).toBeGreaterThan(250_000);
expect(result.completeness.status).toBe("complete");
});
it("retains complete long chunk and WebMCP declaration text", () => {
const specifier = `./${"chunk".repeat(1_000)}.js`;
const name = "tool".repeat(100);
const description = "untrusted declaration ".repeat(150);
const propertyName = "property".repeat(100);
const result = analyzeWebBundleFixture(
inspection(`
import "${specifier}";
document.modelContext.registerTool({
name: "${name}",
description: "${description}",
inputSchema: { properties: { "${propertyName}": { type: "string" } } }
});
`),
);
expect(result.observations.chunks.edges[0]?.specifier).toBe(specifier);
expect(result.observations.webmcp_declarations[0]).toMatchObject({
name,
description,
schema_property_names: [propertyName],
schema_property_names_coverage: "complete",
trust: "page-declared-untrusted",
});
});
});
describe("web bundle source-map coverage", () => {
it("reports unavailable source maps as partial without dropping requested maps", () => {
const analysis = analyzeWebBundleFixture(inspection("export {};"), {
status: "unavailable",
requested: 1,
processed: 1,
items: [
{
script_key: `scr_${"1".repeat(64)}`,
declared_url: "https://app.example.test/app.js.map",
effective_url: null,
status: "fetch_failed",
artifact: null,
original_sources: [],
original_module_edges: [],
mappings: [],
limitation: "Source-map fetch failed.",
},
],
});
expect(analysis.completeness.status).toBe("partial");
expect(analysis.unknowns).toContainEqual({
dimension: "source_maps",
reason:
"One or more requested source maps were unavailable or incomplete",
affected_script_keys: [`scr_${"1".repeat(64)}`],
});
});
it("keeps map-less captured source complete when no maps were requested", () => {
const analysis = analyzeWebBundleFixture(inspection("export {};"), {
status: "not_requested",
requested: 0,
processed: 0,
items: [],
});
expect(analysis.completeness.status).toBe("complete");
expect(analysis.unknowns).toEqual([]);
});
it("rejects inconsistent source-map coverage counts", () => {
const result = analyzeWebBundleFixture(inspection("export {};"));
expect(
webBundleAnalysisSchema.safeParse({
...result,
observations: {
...result.observations,
source_maps: {
...result.observations.source_maps,
requested: 1,
},
},
}).success,
).toBe(false);
});
});
describe("web bundle static-analysis parity", () => {
it("qualifies network spellings through nested dynamic receivers", () => {
const result = analyzeWebBundleFixture(
inspection('window[recv].fetch("/nested-fetch");'),
);
expect(result.observations.endpoints).toEqual([]);
expect(result.inferences).toContainEqual(
expect.objectContaining({
kind: "endpoint_candidate",
value: "/nested-fetch",
confidence: "low",
}),
);
});
it("reads open endpoints only from XMLHttpRequest method and URL pairs", () => {
const result = analyzeWebBundleFixture(
inspection(`
xhr.open("GET", "/xhr-get");
xhr.open("post", "https://xhr.example.test/submit");
dav.open("PROPFIND", "/dav/");
fs.open("/tmp/data.txt", "r", done);
window.open("https://help.example.test/", "_blank");
xhr.open(method, "/dynamic-method");
fs.open(path, "r", done);
window.open(url, "_blank");
window.open(url, "/preview");
globalThis.open(url, "./frame");
document.open("text/html", "/replace");
parent.open("GET", "/parent-xhr");
self.open("post", "/self-xhr");
top.open("GET", "/top-target");
opener.open("GET", "/opener-target");
frames.open("GET", "/frames-target");
globalThis.open("GET", "/global-target");
document.open("GET", "/document-target");
const parentWindow = parent;
parentWindow.open("GET", "/alias-target");
{
const parent = new XMLHttpRequest();
parent.open(method, "/shadowed-parent-xhr");
}
xhr.open(method, "api/relative");
fs.open(path, "w+", done);
popup.open(url, "_TOP");
`),
);
expect(
result.observations.endpoints.map(({ value }) => value).sort(),
).toEqual(["/shadowed-parent-xhr"]);
expect(
result.inferences
.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value)
.sort(),
).toEqual([
"/dav/",
"/dynamic-method",
"/xhr-get",
"api/relative",
"https://xhr.example.test/submit",
]);
expect(result.inferences).toContainEqual(
expect.objectContaining({
kind: "endpoint_candidate",
value: "api/relative",
confidence: "low",
}),
);
});
});
describe("web bundle API classification parity", () => {
it("reads storage open versions as storage rather than endpoints in both analyzers", () => {
const source = `
indexedDB.open(databaseName, "2");
window.indexedDB.open(databaseName, "/v3");
indexedDB.open("APP", "4");
indexedDB.open("GET", "/storage-named-xhr");
`;
expect(
analyzeWebBundleFixture(inspection(source))
.inferences.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value),
).toEqual(["/storage-named-xhr"]);
const analysis = analyzeJavaScriptStaticSource(source);
expect(analysis.endpoints.map(({ value }) => value)).toEqual([
"/storage-named-xhr",
]);
expect(analysis.storage.map(({ kind }) => kind)).toEqual([
"indexed-db",
"indexed-db",
"indexed-db",
"indexed-db",
]);
});
it("uses lexical receiver facts for overloaded open calls", () => {
const result = analyzeWebBundleFixture(
inspection(`
const popup = window;
popup.open(url, "/preview");
{
const indexedDB = { open() {} };
indexedDB.open("PROPFIND", "/dav");
indexedDB.open(databaseName, "2");
}
const xhr = new XMLHttpRequest();
xhr.open(method, "/api");
`),
);
expect(
result.observations.endpoints.map(({ value }) => value).sort(),
).toEqual(["/api"]);
});
it("classifies optional and one-hop aliased open calls", () => {
const source = `
const popup = window;
popup?.open(url, "/preview");
const idb = indexedDB;
idb.open(databaseName, "2");
const xhr = new XMLHttpRequest();
xhr?.open(method, "/api");
`;
const bundle = analyzeWebBundleFixture(inspection(source));
expect(bundle.observations.endpoints.map(({ value }) => value)).toEqual([
"/api",
]);
const staticAnalysis = analyzeJavaScriptStaticSource(source);
expect(staticAnalysis.endpoints.map(({ value }) => value)).toEqual([
"/api",
]);
expect(staticAnalysis.storage.map(({ kind }) => kind)).toEqual([
"indexed-db",
]);
});
it("keeps HTTP-method-named reads of provable keyed collections out of endpoints", () => {
const result = analyzeWebBundleFixture(
inspection(`
axios.get("https://api.example.test/users");
api.get("users");
client.delete("cache-key");
new Map().get("map-key");
new URLSearchParams(location.search).get("q");
new Headers(init).delete("x-trace");
new FormData(form).get("file");
response.headers.get("content-type");
request?.headers.delete("cookie");
new URL(location.href).searchParams.get("page");
api.headers.post("/v1/headers");
api.searchParams.request("/search");
new Headers().get("/users");
api.headers.get("https://api.example.test/headers");
api.searchParams.delete("../search");
`),
);
expect(
result.observations.endpoints.map(({ value }) => value).sort(),
).toEqual([]);
expect(
result.inferences
.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value)
.sort(),
).toEqual([
"../search",
"/search",
"/users",
"/v1/headers",
"cache-key",
"https://api.example.test/headers",
"https://api.example.test/users",
"users",
]);
});
it("keeps undici header-store reads out of endpoint observations", () => {
const result = analyzeWebBundleFixture(
inspection(`
response.headersList.get("location", true);
request.headersList.delete("authorization");
request.headersList.get("/header-list");
api.get("/api/users");
`),
);
expect(result.observations.endpoints).toEqual([]);
expect(
result.inferences
.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value),
).toEqual(["/header-list", "/api/users"]);
});
});
describe("web bundle explicit request reference forms", () => {
it("recognizes URL, path, query and fragment forms after excluding lookup receivers", () => {
const references = [
"http://api.example.test/users",
"https://api.example.test/users?q=1#part",
"ws://api.example.test/events",
"wss://api.example.test/events",
"http:api.example.test/users",
"https:api.example.test/users?q=1&tag=a&tag=b#part",
"https:api.example.test:443/a/../b?x=a+b&x=two#part",
"ws:api.example.test/events",
"wss:api.example.test/events",
"HtTpS:/api.example.test/users",
String.raw`https:\api.example.test\users`,
"https://[invalid",
"//api.example.test/users",
"/api/users",
"./users",
"../users",
"?",
"?page=2&tag=a&tag=b#section",
"#",
"#section",
"#frag?tag=a&tag=b",
];
const ambiguous = [
"",
".",
"..",
"users",
"%23section",
"%3Fpage=2",
" #section",
" https:api.example.test/users",
"https:",
"http:?page=2",
"ws:#section",
"https:[invalid",
"https:api.example.test:99999",
"ftp:api.example.test/users",
"file:/users",
"data:text/plain,users",
];
const source = `
${references.map((value) => `api.get(${JSON.stringify(value)});`).join("\n")}
${ambiguous.map((value) => `api.get(${JSON.stringify(value)});`).join("\n")}
xhr.open(method, "?view=1");
xhr.open(method, "#view=1");
xhr.open("GET", "#literal=1");
xhr.open(method, "https:api.example.test/xhr");
request("POST", target);
snackBar.open(message, "X");
new Map().get("?map-key");
new Map().get("#map-key");
new Map().get("https:api.example.test/map-key");
new Headers().delete("?header-key");
new Headers().delete("#header-key");
new URLSearchParams().get("?parameter-key");
new URLSearchParams().get("#parameter-key");
response.headers.get("?response-key");
response.headers.get("#response-key");
const config = vscode.workspace.getConfiguration("git");
config.get("?setting");
config.get("#setting");
config.get("https:api.example.test/setting");
window.open(url, "?window-target");
window.open(url, "#window-target");
indexedDB.open(name, "?database-version");
indexedDB.open(name, "#database-version");
caches.open(name, "#cache-name");
fs.open(path, "r");
`;
const expected: string[] = [];
const expectedCandidates = [
"?view=1",
"#view=1",
"#literal=1",
"https:api.example.test/xhr",
...references,
...ambiguous,
"POST",
"X",
].sort();
const web = analyzeWebBundleFixture(inspection(source));
expect(web.observations.endpoints.map(({ value }) => value).sort()).toEqual(
expected,
);
expect(
web.inferences
.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value)
.sort(),
).toEqual(expectedCandidates);
const analyzed = analyzeJavaScriptStaticSource(source);
expect(
analyzed.endpoints
.filter(({ interpretation }) => interpretation === undefined)
.map(({ value }) => value)
.sort(),
).toEqual(expected);
expect(
analyzed.endpoints
.filter(({ interpretation }) => interpretation === "candidate")
.map(({ value }) => value)
.sort(),
).toEqual(expectedCandidates);
});
});
describe("web bundle endpoint candidate classification", () => {
it("keeps ambiguous HTTP-method arguments as candidates instead of endpoint observations", () => {
const source = `
fetch("login");
api.get("/api/users");
api.get("users");
form.get("passwordControl");
translate.get("ADDRESS_ADDED");
cookieService.put("token", value);
http.request("POST", target);
snackBar.open(message, "X");
xhr.open(method, "login");
xhr.open("GET", "ping");
`;
const result = analyzeWebBundleFixture(inspection(source));
expect(
result.observations.endpoints.map(({ value }) => value).sort(),
).toEqual(["login"]);
const candidates = result.inferences.filter(
({ kind }) => kind === "endpoint_candidate",
);
expect(candidates.map(({ value }) => value).sort()).toEqual([
"/api/users",
"ADDRESS_ADDED",
"POST",
"X",
"login",
"passwordControl",
"ping",
"token",
"users",
]);
expect(
candidates.every(
({ confidence, basis }) =>
confidence === "low" &&
basis.length === 1 &&
basis[0]?.artifact_sha256 ===
createWebTextArtifact(source, "text/javascript").sha256 &&
basis[0]?.line !== null &&
basis[0]?.detector.startsWith("call:"),
),
).toBe(true);
const staticEndpoints = analyzeJavaScriptStaticSource(source).endpoints;
expect(
staticEndpoints
.filter(({ interpretation }) => interpretation === "candidate")
.map(({ value }) => value)
.sort(),
).toEqual(candidates.map(({ value }) => value).sort());
expect(
staticEndpoints
.filter(({ interpretation }) => interpretation === undefined)
.map(({ value }) => value)
.sort(),
).toEqual(result.observations.endpoints.map(({ value }) => value).sort());
expect(webBundleAnalysisSchema.parse(result)).toEqual(result);
});
it("deduplicates repeated candidate sites while keeping script, value and detector identity", () => {
const source = `${'form.get("name");\n'.repeat(5_000)}translate.get("name");\nform.get("other");`;
const captured = inspection(source);
const first = captured.scripts.items[0];
if (first === undefined) throw new Error("Missing captured script fixture");
const secondKey = `scr_${"2".repeat(64)}`;
const result = analyzeWebBundleFixture({
...captured,
scripts: {
total: 2,
items: [
first,
{
...first,
script_key: secondKey,
url: `${origin}/assets/second.js`,
},
],
},
});
const candidates = result.inferences.filter(
({ kind }) => kind === "endpoint_candidate",
);
expect(candidates).toHaveLength(6);
for (const scriptKey of [first.script_key, secondKey]) {
const local = candidates.filter(
({ basis }) => basis[0]?.script_key === scriptKey,
);
expect(
local.map(({ value, basis }) => [value, basis[0]?.detector]),
).toEqual([
["name", "call:form.get:unverified-network-receiver"],
["name", "call:translate.get:unverified-network-receiver"],
["other", "call:form.get:unverified-network-receiver"],
]);
expect(local[0]?.basis[0]).toMatchObject({
line: 1,
column: 0,
artifact_sha256: createWebTextArtifact(source, "text/javascript")
.sha256,
});
}
expect(result.observations.endpoints).toEqual([]);
expect(result.capture.source_artifacts).toHaveLength(2);
expect(result.completeness).toMatchObject({
status: "complete",
parsed_scripts: 2,
});
expect(result.limitations).toContain(
'Endpoint candidates are deduplicated by script, value and detector; basis retains only the first matching source coordinate. They are not a complete call-site inventory or a runtime occurrence count. completeness.status="complete" describes examination of captured source, not a call-site census.',
);
});
});
describe("web bundle configuration and navigation parity", () => {
it("keeps configuration setting reads out of endpoints in both analyzers", () => {
const source = `
import vscodeDefault from "vscode";
vscodeDefault.workspace.getConfiguration("git").get("default-setting");
vscode.workspace.getConfiguration("git").get("autofetch");
workspace.getConfiguration("git").get("enabled", true);
const config = vscode.workspace.getConfiguration("git");
config.get("autofetchPeriod", 180);
config.get("/v1/settings");
let c = Mt.workspace.getConfiguration("git", uri), l = c.get("timeline.date");
let changed = vscode.workspace.getConfiguration("git");
changed = api;
changed.get("reassigned");
const maybe = flag ? vscode.workspace.getConfiguration("git") : api;
maybe.get("conditional");
{
const config = api;
config.get("shadowed");
}
api.get("users");
const client = getConfiguration("/api/v1");
client.get("accounts");
http.getConfiguration(baseUrl).get("orders");
{
const workspace = { getConfiguration: () => api };
workspace.getConfiguration("/api").get("local-users");
}
{
const vscode = { workspace: { getConfiguration: () => api } };
vscode.workspace.getConfiguration("/api").get("local-orders");
}
`;
const expected = [
"/v1/settings",
"accounts",
"conditional",
"local-orders",
"local-users",
"orders",
"reassigned",
"shadowed",
"users",
];
const web = analyzeWebBundleFixture(inspection(source));
expect(web.observations.endpoints).toEqual([]);
expect(
web.inferences
.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value)
.sort(),
).toEqual(expected);
expect(
analyzeJavaScriptStaticSource(source)
.endpoints.map(({ value }) => value)
.sort(),
).toEqual(expected);
});
it("classifies proven VS Code extension state reads as non-endpoints", () => {
const reads = `
context.globalState.get("lastVersion");
this.workspaceState.get("closedRepositories", []);
context?.globalState.get("onboarded");
context.globalState.get("/v1/state");
context.secrets.get("token");
api.get("users");
`;
const analyze = (source: string) => {
const web = analyzeWebBundleFixture(inspection(source));
const staticAnalysis = analyzeJavaScriptStaticSource(source);
return {
web: {
endpoints: web.observations.endpoints
.map(({ value }) => value)
.sort(),
candidates: web.inferences
.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value)
.sort(),
},
application: {
endpoints: staticAnalysis.endpoints
.filter(({ interpretation }) => interpretation === undefined)
.map(({ value }) => value)
.sort(),
candidates: staticAnalysis.endpoints
.filter(({ interpretation }) => interpretation === "candidate")
.map(({ value }) => value)
.sort(),
},
};
};
const proven = {
endpoints: [],
candidates: ["/v1/state", "token", "users"],
};
expect(analyze(`const vscode = require("vscode");${reads}`)).toEqual({
web: proven,
application: proven,
});
expect(analyze(`import * as vscode from "vscode";${reads}`)).toEqual({
web: proven,
application: proven,
});
const unproven = {
endpoints: [],
candidates: [
"/v1/state",
"closedRepositories",
"lastVersion",
"onboarded",
"token",
"users",
],
};
expect(analyze(`const vscode = require("./vscode");${reads}`)).toEqual({
web: unproven,
application: unproven,
});
});
it("keeps loadURL spelling without Electron receiver proof as a candidate", () => {
const result = analyzeWebBundleFixture(
inspection('window.win.loadURL("https://embedded.test/app");'),
);
expect(result.observations.endpoints).toEqual([]);
expect(result.inferences).toContainEqual(
expect.objectContaining({
kind: "endpoint_candidate",
value: "https://embedded.test/app",
confidence: "low",
}),
);
});
});
describe("web bundle artifact metadata", () => {
it("retains only a validated HTTP target origin", () => {
expect(
analyzeWebBundleFixture(inspection("export {};")).capture.target_origin,
).toBe(origin);
const nonHttp = inspection("export {};");
nonHttp.target.url = "ftp://host/app";
nonHttp.target.origin = "ftp://host";
expect(analyzeWebBundleFixture(nonHttp).capture.target_origin).toBeNull();
});
it("preserves long provider media types in artifact summaries", () => {
const result = analyzeWebBundleFixture(inspection("export {};"));
const mediaType = `application/${"x".repeat(300)}`;
const artifact = result.capture.source_artifacts[0] ?? {
sha256: "0".repeat(64),
bytes: 0,
media_type: "application/javascript",
charset: "utf-8" as const,
text_available: true as const,
};
expect(
webBundleAnalysisSchema.parse({
...result,
capture: {
...result.capture,
source_artifacts: [{ ...artifact, media_type: mediaType }],
},
}).capture.source_artifacts[0]?.media_type,
).toBe(mediaType);
});
});
describe("deep captured web bundle syntax", () => {
it("retains evidence after a parser-admitted deep property chain", () => {
const source = `const value = root${".next".repeat(12_000)};\nimport "./last.js";\nfetch("/after");`;
const result = analyzeWebBundleFixture(inspection(source));
expect(result.completeness).toMatchObject({
status: "complete",
parsed_scripts: 1,
parse_failures: 0,
});
expect(result.observations.chunks.edges).toContainEqual(
expect.objectContaining({
kind: "static_import",
specifier: "./last.js",
resolved_url: `${origin}/assets/last.js`,
location: expect.objectContaining({ line: 2, column: 0 }),
}),
);
expect(result.observations.endpoints).toContainEqual(
expect.objectContaining({
value: "/after",
location: expect.objectContaining({ line: 3, column: 0 }),
}),
);
});
});
const inspection = (source: string) =>
createWebPageInspection([
{
source,
scriptKey: `scr_${"1".repeat(64)}`,
url: `${origin}/assets/app.js`,
origin,
cdpHash: "hash",
},
]);
describe("per-script web bundle analysis", () => {
it("keeps parser failures as analyzed outcomes with their own coverage", () => {
const captured = inspection("function broken( {");
const script = captured.scripts.items.find(isIncludedScript);
if (script === undefined)
throw new Error("Missing included script fixture");
expect(analyzeCapturedWebScript(script)).toMatchObject({
status: "analyzed",
script_key: script.script_key,
parse_status: "failed",
visited_ast_nodes: 0,
observations: {
edges: [],
routes: [],
endpoints: [],
webmcp_declarations: [],
},
});
});
it("merges out-of-order worker outcomes in capture order and preserves exact unavailable keys", () => {
const firstCapture = inspection("import './first.js'; fetch('/first');");
const secondCapture = inspection("import './second.js'; fetch('/second');");
const first = firstCapture.scripts.items.find(isIncludedScript);
const second = secondCapture.scripts.items.find(isIncludedScript);
if (first === undefined || second === undefined)
throw new Error("Missing included script fixtures");
const captured = firstCapture;
captured.scripts.items.push({ ...second, script_key: "second-script" });
captured.scripts.total += 1;
const result = buildCapturedWebBundleAnalysis(
captured,
{
status: "not_requested",
requested: 0,
processed: 0,
items: [],
},
[
{
status: "unavailable",
script_key: "second-script",
reason: "deadline",
detail: "Worker deadline expired.",
},
analyzeCapturedWebScript(first),
],
);
expect(
result.observations.chunks.edges.map(({ specifier }) => specifier),
).toEqual(["./first.js"]);
expect(result.observations.endpoints.map(({ value }) => value)).toEqual([
"/first",
]);
expect(result.unknowns).toContainEqual({
dimension: "javascript_analysis",
reason: expect.stringContaining("deadline"),
affected_script_keys: ["second-script"],
});
expect(result.completeness).toMatchObject({
status: "partial",
parsed_scripts: 1,
parse_failures: 0,
});
});
it("does not count explicitly unstarted scripts as submitted and reports source budget omissions", () => {
const captured = inspection("fetch('/started');");
const script = captured.scripts.items.find(isIncludedScript);
if (script === undefined)
throw new Error("Missing included script fixture");
const later = { ...script, script_key: "not-started-script" };
captured.scripts.items.push(later);
captured.scripts.total += 1;
captured.completeness.excluded.push({
section: "script_sources",
reason: "resource_budget_exhausted",
count: 2,
});
const result = buildCapturedWebBundleAnalysis(
captured,
{
status: "not_requested",
requested: 0,
processed: 0,
items: [],
},
[
analyzeCapturedWebScript(script),
{
status: "unavailable",
script_key: "not-started-script",
reason: "not_started",
},
],
);
expect(result.capture.scripts_analyzed).toBe(1);
expect(result.completeness.status).toBe("partial");
expect(result.unknowns).toContainEqual({
dimension: "script_source",
reason: expect.stringContaining("omitted 2 script source artifacts"),
affected_script_keys: [],
});
expect(result.unknowns).toContainEqual({
dimension: "javascript_analysis",
reason: expect.stringContaining("not_started"),
affected_script_keys: ["not-started-script"],
});
});
});
describe("unavailable web bundle analysis outcomes", () => {
it("preserves distinct unavailable reasons and details with their exact script keys", () => {
const captured = inspection("fetch('/started');");
const first = captured.scripts.items.find(isIncludedScript);
if (first === undefined) throw new Error("Missing included script fixture");
const keys = ["deadline-second", "deadline-different", "resource-failure"];
for (const script_key of keys)
captured.scripts.items.push({ ...first, script_key });
captured.scripts.total += keys.length;
const result = buildCapturedWebBundleAnalysis(
captured,
{
status: "not_requested",
requested: 0,
processed: 0,
items: [],
},
[
{
status: "unavailable",
script_key: first.script_key,
reason: "not_started",
detail: "Total analysis deadline expired after 50 ms.",
},
{
status: "unavailable",
script_key: "deadline-second",
reason: "not_started",
detail: "Total analysis deadline expired after 50 ms.",
},
{
status: "unavailable",
script_key: "deadline-different",
reason: "deadline",
detail: "Per-script deadline expired after 10 ms.",
},
{
status: "unavailable",
script_key: "resource-failure",
reason: "resource_constraint",
detail: "Worker result exceeded its byte budget.",
},
],
);
expect(
result.unknowns.filter(
({ dimension }) => dimension === "javascript_analysis",
),
).toEqual([
{
dimension: "javascript_analysis",
reason:
'Analysis was unavailable for captured scripts (not_started); findings from those scripts are unknown. Worker detail: "Total analysis deadline expired after 50 ms."',
affected_script_keys: [first.script_key, "deadline-second"],
},
{
dimension: "javascript_analysis",
reason:
'Analysis was unavailable for captured scripts (deadline); findings from those scripts are unknown. Worker detail: "Per-script deadline expired after 10 ms."',
affected_script_keys: ["deadline-different"],
},
{
dimension: "javascript_analysis",
reason:
'Analysis was unavailable for captured scripts (resource_constraint); findings from those scripts are unknown. Worker detail: "Worker result exceeded its byte budget."',
affected_script_keys: ["resource-failure"],
},
]);
});
});
it("requires every result reference to belong to its script key", () => {
const captured = inspection(
"import './module.js'; const routes = [{ path: '/known' }]; fetch('/api'); form.get('candidate'); document.modelContext.registerTool({ name: 'lookup' });",
);
const script = captured.scripts.items.find(isIncludedScript);
if (script === undefined) throw new Error("Missing included script fixture");
const outcome = analyzeCapturedWebScript(script);
if (outcome.status !== "analyzed")
throw new Error("Expected a completed script analysis fixture");
const result = outcome;
expect(webBundleScriptAnalysisSchema.parse(result)).toEqual(result);
expect(result.observations.edges.length).toBeGreaterThan(0);
expect(result.observations.routes.length).toBeGreaterThan(0);
expect(result.observations.endpoints.length).toBeGreaterThan(0);
expect(result.observations.webmcp_declarations.length).toBeGreaterThan(0);
expect(result.inferences.length).toBeGreaterThan(0);
const wrongEdgeSource = {
...result,
observations: {
...result.observations,
edges: result.observations.edges.map((edge, index) =>
index === 0 ? { ...edge, from_script_key: "another-script" } : edge,
),
},
};
const wrongEdgeLocation = {
...result,
observations: {
...result.observations,
edges: result.observations.edges.map((edge, index) =>
index === 0
? {
...edge,
location: { ...edge.location, script_key: "another-script" },
}
: edge,
),
},
};
const wrongFindingLocation = {
...result,
observations: {
...result.observations,
endpoints: result.observations.endpoints.map((finding, index) =>
index === 0
? {
...finding,
location: {
...finding.location,
script_key: "another-script",
},
}
: finding,
),
},
};
const wrongRouteLocation = {
...result,
observations: {
...result.observations,
routes: result.observations.routes.map((finding, index) =>
index === 0
? {
...finding,
location: {
...finding.location,
script_key: "another-script",
},
}
: finding,
),
},
};
const wrongWebMcpLocation = {
...result,
observations: {
...result.observations,
webmcp_declarations: result.observations.webmcp_declarations.map(
(declaration, index) =>
index === 0
? {
...declaration,
location: {
...declaration.location,
script_key: "another-script",
},
}
: declaration,
),
},
};
const wrongInferenceBasis = {
...result,
inferences: result.inferences.map((inference, inferenceIndex) =>
inferenceIndex === 0
? {
...inference,
basis: inference.basis.map((basis, basisIndex) =>
basisIndex === 0
? { ...basis, script_key: "another-script" }
: basis,
),
}
: inference,
),
};
expect(webBundleScriptAnalysisSchema.safeParse(wrongEdgeSource).success).toBe(
false,
);
expect(
webBundleScriptAnalysisSchema.safeParse(wrongEdgeLocation).success,
).toBe(false);
expect(
webBundleScriptAnalysisSchema.safeParse(wrongFindingLocation).success,
).toBe(false);
expect(
webBundleScriptAnalysisSchema.safeParse(wrongRouteLocation).success,
).toBe(false);
expect(
webBundleScriptAnalysisSchema.safeParse(wrongWebMcpLocation).success,
).toBe(false);
expect(
webBundleScriptAnalysisSchema.safeParse(wrongInferenceBasis).success,
).toBe(false);
});
it("rejects a failed parse that claims visited nodes or derived facts", () => {
const malformed =
inspection("function broken( {").scripts.items.find(isIncludedScript);
const valid =
inspection("fetch('/api');").scripts.items.find(isIncludedScript);
if (malformed === undefined || valid === undefined)
throw new Error("Missing parse result fixtures");
const failedOutcome = analyzeCapturedWebScript(malformed);
const parsedOutcome = analyzeCapturedWebScript(valid);
if (
failedOutcome.status !== "analyzed" ||
parsedOutcome.status !== "analyzed"
)
throw new Error("Expected completed script analysis fixtures");
const failed = failedOutcome;
const parsed = parsedOutcome;
expect(webBundleScriptAnalysisSchema.parse(failed)).toEqual(failed);
expect(
webBundleScriptAnalysisSchema.safeParse({
...failed,
visited_ast_nodes: 1,
}).success,
).toBe(false);
expect(
webBundleScriptAnalysisSchema.safeParse({
...failed,
widened_return_references: true,
}).success,
).toBe(false);
expect(
webBundleScriptAnalysisSchema.safeParse({
...failed,
observations: {
...failed.observations,
endpoints: parsed.observations.endpoints,
},
}).success,
).toBe(false);
});
it("recognizes expression-free template literals as route properties", () => {
const result = analyzeWebBundleFixture(
inspection(`
const routes = [
{ path: \`score-board\`, component: ScoreBoard },
{ route: \`administration\`, component: Admin },
{ path: \`/user/\${name}\`, component: Dynamic },
];
`),
);
expect(result.observations.routes.map(({ value }) => value)).toEqual([
"score-board",
"administration",
]);
});
it("uses the shared parser for decorated captured scripts", () => {
const result = analyzeWebBundleFixture(
inspection(`@sealed class Widget {} fetch('/decorated');`),
);
expect(result.completeness).toMatchObject({
status: "complete",
parsed_scripts: 1,
parse_failures: 0,
});
expect(result.observations.endpoints).toEqual([
expect.objectContaining({ value: "/decorated" }),
]);
});
it("keeps bare module locations unresolved without inventing URL-relative paths", () => {
const result = analyzeWebBundleFixture(
inspection(`
import "package-name";
import("@scope/package");
require("package-name");
import "./actual.js";
importScripts("worker.js");
`),
);
for (const specifier of ["package-name", "@scope/package"])
expect(
result.observations.chunks.edges
.filter((edge) => edge.specifier === specifier)
.every((edge) => edge.resolved_url === null),
).toBe(true);
expect(
result.observations.chunks.edges.find(
(edge) => edge.specifier === "./actual.js",
)?.resolved_url,
).toBe(`${origin}/assets/actual.js`);
expect(
result.observations.chunks.edges.find(
(edge) => edge.specifier === "worker.js",
)?.resolved_url,
).toBe(`${origin}/assets/worker.js`);
});
it("retains each literal importScripts argument in source order", () => {
const result = analyzeWebBundleFixture(
inspection(
'importScripts("first.js", dynamicValue, "second.js", "first.js");',
),
);
expect(
result.observations.chunks.edges
.filter(({ kind }) => kind === "worker_import")
.map(({ specifier }) => specifier),
).toEqual(["first.js", "second.js"]);
});
describe("web metadata key semantics", () => {
it("keeps computed metadata keys and overwrite order conservative", () => {
const result = analyzeWebBundleFixture(
inspection(`
const path = "description";
const routes = [{ [path]: "/not-a-route" }, { ["path"]: "/real" }];
document.modelContext.registerTool({
name: "old", name: "new",
inputSchema: { properties: { [name]: {}, literal: {} } }
});
document.modelContext.registerTool({ name: "old", ...dynamic });
document.modelContext.registerTool({ name: "old", [name]: "dynamic" });
document.modelContext.registerTool({ name: "old", get name() {} });
document.modelContext.registerTool({ ...dynamic, ["name"]: "restored" });
document.modelContext.registerTool({ name: "valid", [""]: 0 });
`),
);
expect(result.observations.routes.map(({ value }) => value)).toEqual([
"/real",
]);
expect(result.observations.webmcp_declarations).toEqual([
expect.objectContaining({
name: "new",
schema_property_names: ["literal"],
schema_property_names_coverage: "partial",
}),
expect.objectContaining({
name: null,
schema_property_names_coverage: "unknown",
}),
expect.objectContaining({
name: null,
schema_property_names_coverage: "not_present",
}),
expect.objectContaining({
name: "restored",
schema_property_names_coverage: "unknown",
}),
expect.objectContaining({
name: "valid",
schema_property_names_coverage: "not_present",
}),
]);
});
it("projects only exact input schema property names", () => {
const result = analyzeWebBundleFixture(
inspection(`
document.modelContext.registerTool({
name: "literal",
inputSchema: {
type: "object",
properties: { type: {}, properties: {}, "": {}, [dynamicKey]: {}, ...dynamicProperties }
}
});
document.modelContext.registerTool({ name: "missing properties", inputSchema: { type: "object" } });
document.modelContext.registerTool({ name: "dynamic properties", inputSchema: { properties: dynamicProperties } });
document.modelContext.registerTool({ name: "null properties", inputSchema: { properties: null } });
document.modelContext.registerTool({ name: "missing schema" });
document.modelContext.registerTool({ name: "dynamic schema", inputSchema: schema });
document.modelContext.registerTool({ name: "null schema", inputSchema: null });
document.modelContext.registerTool({ name: "spread schema", inputSchema: { ...schema } });
document.modelContext.registerTool({
name: "computed literal keys",
["inputSchema"]: { ["properties"]: { [""]: {}, ["literal"]: {}, [dynamicKey]: {} } }
});
`),
);
expect(
result.observations.webmcp_declarations.map(
({ name, schema_property_names, schema_property_names_coverage }) => ({
name,
schema_property_names,
schema_property_names_coverage,
}),
),
).toEqual([
{
name: "literal",
schema_property_names: ["", "properties", "type"],
schema_property_names_coverage: "partial",
},
{
name: "missing properties",
schema_property_names: [],
schema_property_names_coverage: "not_present",
},
{
name: "dynamic properties",
schema_property_names: [],
schema_property_names_coverage: "unknown",
},
{
name: "null properties",
schema_property_names: [],
schema_property_names_coverage: "unknown",
},
{
name: "missing schema",
schema_property_names: [],
schema_property_names_coverage: "not_present",
},
{
name: "dynamic schema",
schema_property_names: [],
schema_property_names_coverage: "unknown",
},
{
name: "null schema",
schema_property_names: [],
schema_property_names_coverage: "not_present",
},
{
name: "spread schema",
schema_property_names: [],
schema_property_names_coverage: "unknown",
},
{
name: "computed literal keys",
schema_property_names: ["", "literal"],
schema_property_names_coverage: "partial",
},
]);
});
it("keeps missing coverage absent when parsing historical observations", () => {
const analyzed = analyzeWebBundleFixture(
inspection(
`document.modelContext.registerTool({ name: "legacy", inputSchema: { properties: {} } });`,
),
);
const declaration = analyzed.observations.webmcp_declarations[0];
expect(declaration).toBeDefined();
if (declaration === undefined) throw new Error("Expected declaration");
const { schema_property_names_coverage: _coverage, ...legacyDeclaration } =
declaration;
const parsed = webBundleAnalysisSchema.parse({
...analyzed,
observations: {
...analyzed.observations,
webmcp_declarations: [legacyDeclaration],
},
});
expect(
Object.hasOwn(
parsed.observations.webmcp_declarations[0] ?? {},
"schema_property_names_coverage",
),
).toBe(false);
});
});
it("retains WebMCP declarations through optional member and call chaining", () => {
const result = analyzeWebBundleFixture(
inspection(`
document.modelContext?.registerTool({ name: "optional member" });
document.modelContext.registerTool?.({ name: "optional call" });
`),
);
expect(result.observations.webmcp_declarations).toEqual([
expect.objectContaining({
name: "optional member",
trust: "page-declared-untrusted",
}),
expect.objectContaining({
name: "optional call",
trust: "page-declared-untrusted",
}),
]);
});
it("reads expression-free template dynamic imports as chunk edges", () => {
const result = analyzeWebBundleFixture(
inspection(`
import(\`./lazy.js\`);
import("./quoted.js");
import(\`./\${name}.js\`);
`),
);
expect(result.observations.chunks.edges).toEqual([
expect.objectContaining({
kind: "dynamic_import",
specifier: "./lazy.js",
resolved_url: `${origin}/assets/lazy.js`,
}),
expect.objectContaining({
kind: "dynamic_import",
specifier: "./quoted.js",
}),
]);
});
it("does not infer call names from dynamic computed identifiers", () => {
const result = analyzeWebBundleFixture(
inspection(`
const fetch = "render";
const require = "render";
const registerTool = "render";
window[fetch]("/render-only");
window[require]("./not-a-module.js");
document.modelContext[registerTool]({ name: "not-a-tool" });
`),
);
expect(result.observations.endpoints).toEqual([]);
expect(result.observations.chunks.edges).toEqual([]);
expect(result.observations.webmcp_declarations).toEqual([]);
});
it("retains direct and literal computed call names", () => {
const result = analyzeWebBundleFixture(
inspection(`
window.fetch("/direct");
window["fetch"]("/literal");
window[receiver].fetch("/nested-receiver");
window.require("./direct.js");
window["require"]("./literal.js");
document.modelContext["registerTool"]({ name: "literal-tool" });
`),
);
expect(result.observations.endpoints.map(({ value }) => value)).toEqual([
"/direct",
"/literal",
]);
expect(
result.observations.chunks.edges.map(({ specifier }) => specifier),
).toEqual(["./direct.js", "./literal.js"]);
expect(result.observations.webmcp_declarations).toEqual([
expect.objectContaining({ name: "literal-tool" }),
]);
});
it("requires WebMCP receiver and method names to occupy exact member segments", () => {
const result = analyzeWebBundleFixture(
inspection(`
navigator.modelContext.registerTool({ name: "navigator" });
local.modelContext.registerTool({ name: "exact nested" });
const appmodelContext = { registerTool() {} };
appmodelContext.registerTool({ name: "receiver suffix" });
const app = { analyticsmodelContext: { registerTool() {} } };
app.analyticsmodelContext.registerTool({ name: "nested suffix" });
window["modelContext.registerTool"]({ name: "dotted property" });
window["modelContext"].registerTool({ name: "computed receiver" });
`),
);
expect(result.observations.webmcp_declarations).toEqual([
expect.objectContaining({ name: "navigator" }),
expect.objectContaining({ name: "exact nested" }),
expect.objectContaining({ name: "computed receiver" }),
]);
});
it("recognizes tRPC procedures and named GraphQL operations in captured scripts", () => {
const result = analyzeWebBundleFixture(
inspection(`
const n = createTRPCClient({ links: [httpBatchLink({ url: "/api/trpc" })] });
e.invoice.list.useQuery({ competencia: "2026-09" });
n.empresa.byCnpj.query("00000000000191");
const q = "query ListarNotas($competencia: String!) { notas { id } }";
this.client.query({ query: q });
`),
);
expect(
result.observations.endpoints.map(({ value, mechanism }) => ({
value,
mechanism,
})),
).toEqual([
{ value: "/api/trpc", mechanism: "trpc:link:httpBatchLink" },
{ value: "invoice.list", mechanism: "trpc:procedure:useQuery" },
{ value: "empresa.byCnpj", mechanism: "trpc:procedure:query" },
{ value: "ListarNotas", mechanism: "graphql:query" },
]);
});
it("keeps RPC lexical identity and bundled factory recognition in captured scripts", () => {
const longOperation = "Operation" + "x".repeat(512);
const document =
"#" +
"comment".repeat(700) +
"\nquery " +
longOperation +
" { users { id } }";
const result = analyzeWebBundleFixture(
inspection(`
function first() { const c = createTRPCClient({}); }
function unrelated(c) { c.users.query("select 1"); }
const c = (0, t.createTRPCClient)({ links: [(0, t.httpBatchLink)({ url: "/api/rpc" })] });
c.posts.query();
const doc = ${JSON.stringify(document)};
`),
);
expect(
result.observations.endpoints.map(({ value, mechanism }) => ({
value,
mechanism,
})),
).toEqual([
{ value: "/api/rpc", mechanism: "trpc:link:httpBatchLink" },
{ value: "posts", mechanism: "trpc:procedure:query" },
{ value: longOperation, mechanism: "graphql:query" },
]);
expect(result.completeness.status).toBe("complete");
});
it("retains distinct WebMCP descriptions and names while deduplicating equivalent properties", () => {
const result = analyzeWebBundleFixture(
inspection(`
document.modelContext.registerTool({ name: "lookup", description: "first", inputSchema: { properties: { alpha: {} } } });
document.modelContext.registerTool({ name: "lookup", description: "second", inputSchema: { properties: { alpha: {} } } });
document.modelContext.registerTool({ name: "equivalent", inputSchema: { properties: { alpha: {}, beta: {} } } });
document.modelContext.registerTool({ name: "equivalent", inputSchema: { properties: { beta: {}, alpha: {}, alpha: {} } } });
document.modelContext.registerTool({ name: "empty-status", inputSchema: { properties: {} } });
document.modelContext.registerTool({ name: "empty-status", inputSchema: { properties: dynamicProperties } });
document.modelContext.registerTool({ description: "unnamed" });
document.modelContext.registerTool({ name: "", description: "unnamed" });
`),
);
expect(
result.observations.webmcp_declarations.map(
({
name,
description,
schema_property_names,
schema_property_names_coverage,
}) => ({
name,
description,
schema_property_names,
schema_property_names_coverage,
}),
),
).toEqual([
{
name: "lookup",
description: "first",
schema_property_names: ["alpha"],
schema_property_names_coverage: "complete",
},
{
name: "lookup",
description: "second",
schema_property_names: ["alpha"],
schema_property_names_coverage: "complete",
},
{
name: "equivalent",
description: null,
schema_property_names: ["alpha", "beta"],
schema_property_names_coverage: "complete",
},
{
name: "empty-status",
description: null,
schema_property_names: [],
schema_property_names_coverage: "complete",
},
{
name: "empty-status",
description: null,
schema_property_names: [],
schema_property_names_coverage: "unknown",
},
{
name: null,
description: "unnamed",
schema_property_names: [],
schema_property_names_coverage: "not_present",
},
{
name: "",
description: "unnamed",
schema_property_names: [],
schema_property_names_coverage: "not_present",
},
]);
expect(result.observations.webmcp_declarations[2]?.location).toEqual({
script_key: `scr_${"1".repeat(64)}`,
line: 4,
column: 6,
});
expect(
result.observations.webmcp_declarations.every(
({ trust }) => trust === "page-declared-untrusted",
),
).toBe(true);
});
it("keeps known capture omissions in compact route summaries and queryable gap facts", () => {
const captured = inspection("createBrowserRouter([{path:'/reports'}]);");
captured.completeness.excluded.push(
{ section: "scripts", reason: "resource_budget_exhausted", count: 3 },
{
section: "script_sources",
reason: "resource_budget_exhausted",
count: null,
},
);
const report = analyzeWebBundleFixture(captured),
parent = {
evidenceId: `ev_${"a".repeat(64)}`,
operation: "analyze_web_bundle",
normalizedResult: report,
limitations: report.limitations,
subject: null,
};
const summary = projectAnalysisView(
parent,
analysisViewRequestSchema.parse({
kind: "javascript-routes",
collection: "summary",
}),
);
if (!summary.ok) throw summary.error;
if (summary.value.kind !== "javascript-routes") throw new Error("Wrong view");
expect(summary.value.source_coverage.candidate_enumeration).toBe("partial");
expect(summary.value.summary!.reported_capture_omissions).toEqual(
expect.arrayContaining([
expect.objectContaining({ unit: "script-records", count: 3 }),
expect.objectContaining({ unit: "script-sources", count: null }),
]),
);
const gaps = projectAnalysisView(
parent,
analysisViewRequestSchema.parse({
kind: "javascript-routes",
collection: "unknowns",
where: { kinds: ["capture-omission"] },
fields: ["capture_omission", "reason"],
}),
);
if (!gaps.ok) throw gaps.error;
if (gaps.value.kind !== "javascript-routes") throw new Error("Wrong view");
expect(gaps.value.items).toHaveLength(2);
for (const row of gaps.value.items)
expect(row).toMatchObject({ source_id: null, provenance: null });
});
it("retains captured Next script identity while deriving decoded route patterns", () => {
const url = `${origin}/base/_next/static/chunks/app/%28shop%29/blog/%5Bid%5D/page-abc.js`;
const captured = createWebPageInspection([
{
source: "router.push('/help');",
scriptKey: `scr_${"2".repeat(64)}`,
url,
origin,
cdpHash: "hash",
},
]);
const result = analyzeWebBundleFixture(captured);
expect(result.route_inventory?.routes.map(({ path }) => path)).toEqual([
"/base/blog/:id",
"/base/help",
]);
expect(result.route_inventory?.sources).toEqual([
expect.objectContaining({
url,
sha256: captured.scripts.items[0]?.source.included
? captured.scripts.items[0].source.artifact.sha256
: null,
}),
]);
expect(result.route_inventory?.occurrences).toEqual(
expect.arrayContaining([
expect.objectContaining({
origin: "source-url",
unprefixed_path: "/blog/:id",
inferred_base_path: "/base",
}),
expect.objectContaining({
origin: "source-syntax",
unprefixed_path: "/help",
}),
]),
);
});
it("qualifies local browser API spellings while preserving intrinsic requests", () => {
const result = analyzeWebBundleFixture(
inspection(`
const local = { fetch(x) { return x; }, sendBeacon(x) { return x; }, EventSource(x) { return x; } };
local.fetch('/local-fetch'); local.sendBeacon('/local-beacon'); local.EventSource('/local-events');
const other = { open(a,b) { return b; }, post(x) { return x; }, put(x) { return x; }, patch(x) { return x; }, request(x) { return x; } };
other.open('GET', '/local-open'); other.post('/local-post'); other.put('/local-put'); other.patch('/local-patch'); other.request('/local-request');
fetch('/intrinsic-fetch'); navigator.sendBeacon('/intrinsic-beacon'); new EventSource('/intrinsic-events');
`),
);
expect(
result.observations.endpoints.map(({ value }) => value).sort(),
).toEqual(["/intrinsic-beacon", "/intrinsic-events", "/intrinsic-fetch"]);
expect(
result.inferences
.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value)
.sort(),
).toEqual([
"/local-beacon",
"/local-events",
"/local-fetch",
"/local-open",
"/local-patch",
"/local-post",
"/local-put",
"/local-request",
]);
});
it.each([
["", true],
["xhr.open = replacement;", false],
["XMLHttpRequest.prototype.open = replacement;", false],
["const alias = xhr; alias.open = replacement;", false],
["mutate(xhr);", false],
])(
"preserves XHR receiver proof across visible mutation: %s",
(mutation, observed) => {
const source = `const xhr = new XMLHttpRequest(); ${mutation} xhr.open('GET', '/xhr');`;
const result = analyzeWebBundleFixture(inspection(source));
expect(result.observations.endpoints.map(({ value }) => value)).toEqual(
observed ? ["/xhr"] : [],
);
expect(
result.inferences
.filter(({ kind }) => kind === "endpoint_candidate")
.map(({ value }) => value),
).toEqual(observed ? [] : ["/xhr"]);
expect(analyzeJavaScriptStaticSource(source).endpoints).toContainEqual(
expect.objectContaining({
value: "/xhr",
...(observed ? {} : { interpretation: "candidate" }),
}),
);
},
);