From aaf4d210688d12b0fc5d2f95c350d480511d6014 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Thu, 29 Oct 2026 01:29:59 +0800 Subject: [PATCH] fix(source-map): clarify policy and evidence provenance (#7828) --- docs/web-source-location.md | 7 +++-- .../WebSourceLocationService.test.ts | 17 +++++++++++ src/application/WebSourceLocationService.ts | 4 +-- src/browser/WebSourceMapFetcher.ts | 4 +-- tests/acceptance/webSourceLocationCli.test.ts | 29 +++++++++++++++++-- .../browser/webSourceMapFetcher.test.ts | 28 +++++++++++++++--- 6 files changed, 77 insertions(+), 12 deletions(-) diff --git a/docs/web-source-location.md b/docs/web-source-location.md index 8e24bf533a..20de709568 100644 --- a/docs/web-source-location.md +++ b/docs/web-source-location.md @@ -101,8 +101,11 @@ run token while process release is unconfirmed, and the directory until removal succeeds. A later successful retry settles every error bound to that command; historical failure diagnostics remain available. -Complete evidence is bounded by a 4 MiB map, 32 MiB codec reply, 262144 decoded -rows/segments and 64 nested section levels. Those structural budgets and the +Complete evidence is bounded by a 4 MiB map, 32 MiB codec reply and a shared +262144-record decoder budget. Generated rows, mapping segments, leaf maps, +source and name declarations, `sourcesContent` entries and indexed sections all +consume this budget. Generated rows and mapping segments also each have a +262144 limit; indexed maps allow at most 64 nested section levels. Those budgets and the 192 MiB codec heap have no runtime override. Exceeding a budget returns an explicit failure, without partial results. The existing 32 MiB manifest and 16 MiB selected script boundaries also apply. No global toolchain or browser configuration changes diff --git a/src/application/WebSourceLocationService.test.ts b/src/application/WebSourceLocationService.test.ts index 20de278521..7faa175c3a 100644 --- a/src/application/WebSourceLocationService.test.ts +++ b/src/application/WebSourceLocationService.test.ts @@ -60,6 +60,23 @@ it("passes an explicit decode deadline and omits the default from Evidence", asy expect(seen).toEqual([250, undefined]); }); +it("reports shared decoder record costs in result and Evidence limitations", async () => { + const response = await service().trace(args); + if (!response.ok) throw response.error; + const result = webSourceLocationResultSchema.parse( + response.value.normalized_result, + ); + for (const limitations of [response.value.limitations, result.limitations]) { + expect(limitations).toEqual( + expect.arrayContaining([ + expect.stringContaining( + "shared decoder budget for generated rows, mapping segments, leaf maps, source and name declarations, sourcesContent entries, and indexed sections", + ), + ]), + ); + } +}); + it.each([ { ...args, manifest_path: "relative.json" }, { ...args, script_index: -1 }, diff --git a/src/application/WebSourceLocationService.ts b/src/application/WebSourceLocationService.ts index 42ae1146f5..90244172d0 100644 --- a/src/application/WebSourceLocationService.ts +++ b/src/application/WebSourceLocationService.ts @@ -50,13 +50,13 @@ const artifactsSchema = selectedWebScriptArtifactsSchema.extend({ const LIMITATIONS = [ "The source/map pairing and source-map URL context are caller-selected. Deployment authenticity and execution remain unknown.", "Capture metadata and its Evidence reference are historical context; this trace independently verifies only the selected manifest, script and map bytes.", - "Source names and sourceRoot are reported declarations. Resolved URLs are derived by the pinned upstream codec; no original source is fetched or executed.", + "Source names and sourceRoot are reported declarations. REA derives resolved URLs using WHATWG URL resolution; the pinned upstream codec decodes mappings. No original source is fetched or executed.", "Embedded source hashes identify the UTF-8 encoding of decoded sourcesContent text, not an independently retained original file. Missing or out-of-range original content positions remain explicit.", "Lookup returns all segments at the greatest generated position not greater than the selected point, including earlier lines and generated-only segments. A mapped point does not establish an executed range or UI causality.", "Regular maps and inline indexed sections are supported. External section URLs are unsupported; unknown extensions are retained in raw map text and are not interpreted.", ]; const codecBudget = (timeoutMs: number): string => - `Complete evidence has a 4 MiB map, 32 MiB reply, 262144 decoded rows/segments and 64-level section budget. The owned codec has a ${String(timeoutMs)} ms decode_timeout_ms deadline`; + `Complete evidence has a 4 MiB map, 32 MiB reply and a ${String(WEB_SOURCE_MAP_LIMITS.decodedRecords)}-record shared decoder budget for generated rows, mapping segments, leaf maps, source and name declarations, sourcesContent entries, and indexed sections. Generated rows and mapping segments have separate ${String(WEB_SOURCE_MAP_LIMITS.decodedRows)} and ${String(WEB_SOURCE_MAP_LIMITS.decodedSegments)} limits, and indexed maps have a ${String(WEB_SOURCE_MAP_LIMITS.sectionDepth)}-level section budget. The owned codec has a ${String(timeoutMs)} ms decode_timeout_ms deadline`; /** Bun accepts but ignores the codec's V8 heap flags; Windows enforces a commit limit instead. */ const codecBudgetLimitation = ( runtime: WebSourceMapReport["runtime"], diff --git a/src/browser/WebSourceMapFetcher.ts b/src/browser/WebSourceMapFetcher.ts index d71f3a9a26..f934953109 100644 --- a/src/browser/WebSourceMapFetcher.ts +++ b/src/browser/WebSourceMapFetcher.ts @@ -331,7 +331,7 @@ const fetchOne = async ( return emptySourceMapItem( request, "policy_filtered", - "Declared source-map URL is outside the approved exact origins.", + "Declared source-map URL must be a credential-free HTTP(S) URL on an approved exact origin.", ); let effectiveUrl: string | null = null; let completedResponseIdentity: ResponseIdentity | undefined; @@ -349,7 +349,7 @@ const fetchOne = async ( return emptySourceMapItem( request, "policy_filtered", - "A source-map redirect left the approved exact origins.", + "Each source-map redirect must be a credential-free HTTP(S) URL on an approved exact origin.", effectiveUrl, ); const { response, fetchedUrl } = fetched; diff --git a/tests/acceptance/webSourceLocationCli.test.ts b/tests/acceptance/webSourceLocationCli.test.ts index 2efc953468..f5bee3d4f4 100644 --- a/tests/acceptance/webSourceLocationCli.test.ts +++ b/tests/acceptance/webSourceLocationCli.test.ts @@ -18,7 +18,15 @@ cliTest( "accepts numeric UTF-16 coordinates and runs the actual owned source-map codec without a browser", async ({ cli }) => { const root = await createTestTempDirectory("rea-source-map-cli-"); - const fixture = webSourceLocationFixture(); + const fixture = webSourceLocationFixture( + JSON.stringify({ + version: 3, + sources: ["dir/original.ts", "dir//original.ts"], + sourcesContent: ["original", "second"], + names: [], + mappings: "AAAA,ACAA", + }), + ); await mkdir(join(root, "files", "modules"), { recursive: true }); await writeFile(join(root, "files", "modules", "main.js"), fixture.source); let manifest = join(root, "manifest.json").replaceAll("\\", "/"); @@ -63,12 +71,29 @@ cliTest( source_map: { association: "caller-selected" }, generated_offset: 0, execution: "unknown", - matches: [{ content: { text: "original" } }], + matches: [ + { + source_index: 0, + resolved_url: "https://app.test/dir/original.ts", + content: { text: "original" }, + }, + { + source_index: 1, + resolved_url: "https://app.test/dir//original.ts", + content: { text: "second" }, + }, + ], runtime: { id: process.versions.bun === undefined ? "node-v8" : "bun", }, }, }); + const limitation = + "Source names and sourceRoot are reported declarations. REA derives resolved URLs using WHATWG URL resolution; the pinned upstream codec decodes mappings. No original source is fetched or executed."; + expect(response.json).toMatchObject({ + limitations: expect.arrayContaining([limitation]), + normalized_result: { limitations: expect.arrayContaining([limitation]) }, + }); const transport = new StdioClientTransport({ command: process.execPath, args: [ diff --git a/tests/boundary/browser/webSourceMapFetcher.test.ts b/tests/boundary/browser/webSourceMapFetcher.test.ts index e933933711..2117969338 100644 --- a/tests/boundary/browser/webSourceMapFetcher.test.ts +++ b/tests/boundary/browser/webSourceMapFetcher.test.ts @@ -551,7 +551,10 @@ describe("web source-map fetching and validation: fetching maps and following re expectInvalidIncludedSourceMaps(result); }); - it("reauthorizes every redirect and never contacts a disallowed origin", async () => { + it.each([ + ["another origin", "https://private.example.test/map"], + ["same-origin credentials", "https://user:secret@app.example.test/map"], + ])("reauthorizes a redirect with %s", async (_case, location) => { const calls: string[] = []; const result = await fetchWebSourceMapsOk([request], input(), undefined, { fetch: (url) => { @@ -559,23 +562,33 @@ describe("web source-map fetching and validation: fetching maps and following re return Promise.resolve( new Response(null, { status: 302, - headers: { location: "https://private.example.test/map" }, + headers: { location }, }), ); }, }); expect(calls).toEqual([request.fetchUrl]); + expect(JSON.stringify(result)).not.toContain("user:secret"); expect(result).toMatchObject({ status: "unavailable", - items: [{ status: "policy_filtered", effective_url: request.fetchUrl }], + items: [ + { + status: "policy_filtered", + effective_url: request.fetchUrl, + limitation: + "Each source-map redirect must be a credential-free HTTP(S) URL on an approved exact origin.", + }, + ], }); expectInvalidSourceMaps({ ...result, items: [{ ...result.items[0], limitation: null }], }); }); +}); +describe("source-map declared URL policy", () => { it.each([ ["embedded credentials", "https://user:secret@app.example.test/a.map"], ["a different scheme on the same host", "http://app.example.test/a.map"], @@ -602,7 +615,14 @@ describe("web source-map fetching and validation: fetching maps and following re expect(calls).toEqual([]); expect(result).toMatchObject({ status: "unavailable", - items: [{ status: "policy_filtered", effective_url: null }], + items: [ + { + status: "policy_filtered", + effective_url: null, + limitation: + "Declared source-map URL must be a credential-free HTTP(S) URL on an approved exact origin.", + }, + ], }); }, );