[GH-ISSUE #6726] [Bug] Source-map tracing collapses distinct source URLs with repeated slashes #4090

Closed
opened 2026-10-31 00:46:45 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

The published 6.7.0 trace-web-source-location CLI succeeds but gives dir/original.ts and dir//original.ts the same resolved_url, https://app.test/maps/dir/original.ts. The second declaration should resolve to https://app.test/maps/dir//original.ts. Both source indices and their different embedded texts remain present. Current main reproduces the same mismatch through its production decoder function.

Reproduction

Set REA_PACKAGE_ROOT to an extracted or installed rea-agents@6.7.0 package root with its dependencies available. This reproduces the fixture and CLI invocation executed; its temporary state contains no ambient configuration or private data. The manifest deliberately uses synthetic historical capture metadata, which this documented workflow permits; it independently verifies the selected manifest, script and map bytes.

node --input-type=module <<'JS'
import {mkdtempSync,mkdirSync,writeFileSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join,resolve} from 'node:path';
import {createHash} from 'node:crypto';
import {spawnSync} from 'node:child_process';
const packageRoot=resolve(process.env.REA_PACKAGE_ROOT);
const root=mkdtempSync(join(tmpdir(),'rea-source-url-'));
mkdirSync(join(root,'files/modules'),{recursive:true});
const source='generated';
const manifest={
  capture_path:join(root,'capture.json'),capture_sha256:'a'.repeat(64),
  capture_kind:'browser-scenario',source_evidence_id:null,
  capture_completeness:{status:'complete',equality_eligible:true,
    missing_sections:[],truncated_sections:[]},
  output_directory:root,analysis_input:{input_path:root,format:'directory'},
  limitations:[],scripts:[{
    url:'https://app.test/main.js',
    source:{kind:'scenario-response',transaction_id:'txn',
      request_sequence:1,response_sequence:2,status:200},
    content:{state:'exported',relative_path:'modules/main.js',layout:'isolated',
      layout_reason:'fixture',sha256:createHash('sha256').update(source).digest('hex'),
      bytes:Buffer.byteLength(source),media_type:'text/javascript',redacted:false,
      representation:'browser-decoded-response-bytes'}
  }]
};
writeFileSync(join(root,'files/modules/main.js'),source,{flag:'wx'});
writeFileSync(join(root,'manifest.json'),JSON.stringify(manifest),{flag:'wx'});
writeFileSync(join(root,'app.js.map'),JSON.stringify({version:3,
  sources:['dir/original.ts','dir//original.ts'],sourcesContent:['first','second'],
  names:[],mappings:'AAAA,ACAA'}),{flag:'wx'});
const env={PATH:process.env.PATH,HOME:join(root,'state/home'),
  XDG_CONFIG_HOME:join(root,'state/config'),XDG_CACHE_HOME:join(root,'state/cache'),
  XDG_DATA_HOME:join(root,'state/data')};
for(const k of ['HOME','XDG_CONFIG_HOME','XDG_CACHE_HOME','XDG_DATA_HOME'])
  mkdirSync(env[k],{recursive:true});
const r=spawnSync(process.execPath,[join(packageRoot,'scripts/rea.mjs'),
  'trace-web-source-location',join(root,'manifest.json'),'0',join(root,'app.js.map'),
  'https://app.test/maps/app.js.map','1','0','--json'],
  {env,encoding:'utf8',timeout:30000,maxBuffer:4*1024*1024});
console.log(JSON.stringify({exit:r.status,stderr:r.stderr}));
if(r.status!==0)throw new Error(r.stdout);
console.log(JSON.parse(r.stdout).normalized_result.matches.map(
  ({source_index,reported_source,resolved_url})=>
    ({source_index,reported_source,resolved_url})));
console.log(new URL('dir//original.ts','https://app.test/maps/app.js.map').href);
JS

One completed published CLI attempt and one current-main decoder attempt exhibited the defect. The fixture contains a single-slash control at the same generated point. No original URL is fetched and no target code executes.

Expected behavior

Return https://app.test/maps/dir/original.ts for source index 0 and https://app.test/maps/dir//original.ts for index 1, preserving their indices and embedded contents. Internal empty URL path segments are significant; the retained context does not establish that these distinct source URLs identify the same resource.

ECMA-426 DecodeSourceMapSources, steps 6.3.1–6.3.4 requires URL parsing against the map base after prepending any sourceRoot. This fixture has no sourceRoot. Standard new URL(source, context).href preserves its repeated slash. REA's source-location guide documents derived original-source identities and support for regular maps.

Impact and scope

A valid ordinary source map returns an incorrect original-source URL and makes two distinct source URLs appear equal in that field. The raw declarations, separate indices and embedded texts survive, so consumers can reconstruct the correct URLs from the retained declarations and context. The demonstrated scope is this regular-map fixture; real-site frequency and effects on downstream consumers were not measured.

REA version

Published npm tarball rea-agents@6.7.0, downloaded from the npm registry and run through its shipped CLI/runtime with the inspection checkout's locked dependencies. Both the release manifest and checkout lock pin the relevant @jridgewell/trace-mapping dependency to 0.3.31. Also executed the source decoder at main commit 5d6b8a0ab693c03ab85cf0807d40bedf021e76d8; main's complete CLI was not built or executed.

Runtime and platform

Node.js v24.22.1; Linux x86_64. No browser, Java runtime, disassembler or analysis engine was needed.

Target and artifact context

Caller-selected retained script with synthetic historical manifest metadata and the 132-byte version 3 map shown above. Two mappings share generated line 1, column 0 and refer to separate declared sources with distinct embedded contents. Source/map authenticity and target execution are not claimed.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The shipped CLI exited 0 with empty stderr and decoder engine jridgewell-trace-mapping version 0.3.31:

[
  {"source_index":0,"reported_source":"dir/original.ts","resolved_url":"https://app.test/maps/dir/original.ts"},
  {"source_index":1,"reported_source":"dir//original.ts","resolved_url":"https://app.test/maps/dir/original.ts"}
]

The independently executed URL comparison returned https://app.test/maps/dir//original.ts for the second source. Its first-source control agreed with REA. The exact map SHA-256 was 1a3cc9251c37434e7bc48d65d7641b5d30005b1b13e1e5e73fbef48f556acc72.

Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete. Current-main source evidence: TraceSourceMap.ts lines 175–185 retains reported_source but publishes the upstream codec's resolvedSources value.

Open/closed searches covered source-map URL, sourceRoot, slash, repeated and empty-segment terms. Related #6435 concerns losing the authority with a pathless context. This context already has an explicit /maps/app.js.map path and preserves its authority; the empty source pathname segment is lost instead. Related #6696 concerns environment collection keys, not this decoder. Both related bodies were read. No matching repeated-slash tracing report was found.

MCP, browser fetching, other platforms and downstream consumers were not run. No implementation change or restoration run was performed. Per the research instruction, the bug was not rerun after this draft was written.

Analysis

Source-derived: REA passes the map to its pinned TraceMap implementation and directly publishes that implementation's resolved source URL. The observed normalization also occurs in the currently pinned codec. This is a derived-identity defect; it does not merge the retained source declarations or remove embedded contents. A restoration check should preserve the single-slash control and the repeated-slash URL while retaining both candidates and their contents.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The released public CLI reports the wrong derived original-source identity for a bounded valid map, while retaining the raw source declaration and embedded text; downstream production damage was not observed.
  • Workaround status: available
  • Workaround basis: For the demonstrated absent-sourceRoot case, consumers can derive the correct URL from retained reported_source and url_context using standard URL parsing. The executed comparison produced the expected distinct URLs without changing the input.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6726 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Evidence, artifacts, or reconstruction ## Observed behavior The published 6.7.0 `trace-web-source-location` CLI succeeds but gives `dir/original.ts` and `dir//original.ts` the same `resolved_url`, `https://app.test/maps/dir/original.ts`. The second declaration should resolve to `https://app.test/maps/dir//original.ts`. Both source indices and their different embedded texts remain present. Current main reproduces the same mismatch through its production decoder function. ## Reproduction Set `REA_PACKAGE_ROOT` to an extracted or installed `rea-agents@6.7.0` package root with its dependencies available. This reproduces the fixture and CLI invocation executed; its temporary state contains no ambient configuration or private data. The manifest deliberately uses synthetic historical capture metadata, which this documented workflow permits; it independently verifies the selected manifest, script and map bytes. ```sh node --input-type=module <<'JS' import {mkdtempSync,mkdirSync,writeFileSync} from 'node:fs'; import {tmpdir} from 'node:os'; import {join,resolve} from 'node:path'; import {createHash} from 'node:crypto'; import {spawnSync} from 'node:child_process'; const packageRoot=resolve(process.env.REA_PACKAGE_ROOT); const root=mkdtempSync(join(tmpdir(),'rea-source-url-')); mkdirSync(join(root,'files/modules'),{recursive:true}); const source='generated'; const manifest={ capture_path:join(root,'capture.json'),capture_sha256:'a'.repeat(64), capture_kind:'browser-scenario',source_evidence_id:null, capture_completeness:{status:'complete',equality_eligible:true, missing_sections:[],truncated_sections:[]}, output_directory:root,analysis_input:{input_path:root,format:'directory'}, limitations:[],scripts:[{ url:'https://app.test/main.js', source:{kind:'scenario-response',transaction_id:'txn', request_sequence:1,response_sequence:2,status:200}, content:{state:'exported',relative_path:'modules/main.js',layout:'isolated', layout_reason:'fixture',sha256:createHash('sha256').update(source).digest('hex'), bytes:Buffer.byteLength(source),media_type:'text/javascript',redacted:false, representation:'browser-decoded-response-bytes'} }] }; writeFileSync(join(root,'files/modules/main.js'),source,{flag:'wx'}); writeFileSync(join(root,'manifest.json'),JSON.stringify(manifest),{flag:'wx'}); writeFileSync(join(root,'app.js.map'),JSON.stringify({version:3, sources:['dir/original.ts','dir//original.ts'],sourcesContent:['first','second'], names:[],mappings:'AAAA,ACAA'}),{flag:'wx'}); const env={PATH:process.env.PATH,HOME:join(root,'state/home'), XDG_CONFIG_HOME:join(root,'state/config'),XDG_CACHE_HOME:join(root,'state/cache'), XDG_DATA_HOME:join(root,'state/data')}; for(const k of ['HOME','XDG_CONFIG_HOME','XDG_CACHE_HOME','XDG_DATA_HOME']) mkdirSync(env[k],{recursive:true}); const r=spawnSync(process.execPath,[join(packageRoot,'scripts/rea.mjs'), 'trace-web-source-location',join(root,'manifest.json'),'0',join(root,'app.js.map'), 'https://app.test/maps/app.js.map','1','0','--json'], {env,encoding:'utf8',timeout:30000,maxBuffer:4*1024*1024}); console.log(JSON.stringify({exit:r.status,stderr:r.stderr})); if(r.status!==0)throw new Error(r.stdout); console.log(JSON.parse(r.stdout).normalized_result.matches.map( ({source_index,reported_source,resolved_url})=> ({source_index,reported_source,resolved_url}))); console.log(new URL('dir//original.ts','https://app.test/maps/app.js.map').href); JS ``` One completed published CLI attempt and one current-main decoder attempt exhibited the defect. The fixture contains a single-slash control at the same generated point. No original URL is fetched and no target code executes. ## Expected behavior Return `https://app.test/maps/dir/original.ts` for source index 0 and `https://app.test/maps/dir//original.ts` for index 1, preserving their indices and embedded contents. Internal empty URL path segments are significant; the retained context does not establish that these distinct source URLs identify the same resource. [ECMA-426 DecodeSourceMapSources, steps 6.3.1–6.3.4](https://tc39.es/ecma426/#sec-decodesourcemapsources) requires URL parsing against the map base after prepending any sourceRoot. This fixture has no sourceRoot. Standard `new URL(source, context).href` preserves its repeated slash. [REA's source-location guide](https://github.com/morluto/rea/blob/5d6b8a0ab693c03ab85cf0807d40bedf021e76d8/docs/web-source-location.md#meaning-of-the-evidence) documents derived original-source identities and support for regular maps. ## Impact and scope A valid ordinary source map returns an incorrect original-source URL and makes two distinct source URLs appear equal in that field. The raw declarations, separate indices and embedded texts survive, so consumers can reconstruct the correct URLs from the retained declarations and context. The demonstrated scope is this regular-map fixture; real-site frequency and effects on downstream consumers were not measured. ## REA version Published npm tarball `rea-agents@6.7.0`, downloaded from the npm registry and run through its shipped CLI/runtime with the inspection checkout's locked dependencies. Both the release manifest and checkout lock pin the relevant `@jridgewell/trace-mapping` dependency to `0.3.31`. Also executed the source decoder at main commit `5d6b8a0ab693c03ab85cf0807d40bedf021e76d8`; main's complete CLI was not built or executed. ## Runtime and platform Node.js `v24.22.1`; Linux x86_64. No browser, Java runtime, disassembler or analysis engine was needed. ## Target and artifact context Caller-selected retained script with synthetic historical manifest metadata and the 132-byte version 3 map shown above. Two mappings share generated line 1, column 0 and refer to separate declared sources with distinct embedded contents. Source/map authenticity and target execution are not claimed. ## Interface CLI ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence The shipped CLI exited 0 with empty stderr and decoder engine `jridgewell-trace-mapping` version `0.3.31`: ```json [ {"source_index":0,"reported_source":"dir/original.ts","resolved_url":"https://app.test/maps/dir/original.ts"}, {"source_index":1,"reported_source":"dir//original.ts","resolved_url":"https://app.test/maps/dir/original.ts"} ] ``` The independently executed URL comparison returned `https://app.test/maps/dir//original.ts` for the second source. Its first-source control agreed with REA. The exact map SHA-256 was `1a3cc9251c37434e7bc48d65d7641b5d30005b1b13e1e5e73fbef48f556acc72`. Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete. Current-main source evidence: [TraceSourceMap.ts lines 175–185](https://github.com/morluto/rea/blob/5d6b8a0ab693c03ab85cf0807d40bedf021e76d8/src/javascript/sourceMaps/TraceSourceMap.ts#L175-L185) retains `reported_source` but publishes the upstream codec's `resolvedSources` value. Open/closed searches covered source-map URL, sourceRoot, slash, repeated and empty-segment terms. Related [#6435](https://github.com/morluto/rea/issues/6435) concerns losing the authority with a pathless context. This context already has an explicit `/maps/app.js.map` path and preserves its authority; the empty source pathname segment is lost instead. Related [#6696](https://github.com/morluto/rea/issues/6696) concerns environment collection keys, not this decoder. Both related bodies were read. No matching repeated-slash tracing report was found. MCP, browser fetching, other platforms and downstream consumers were not run. No implementation change or restoration run was performed. Per the research instruction, the bug was not rerun after this draft was written. ## Analysis Source-derived: REA passes the map to its pinned `TraceMap` implementation and directly publishes that implementation's resolved source URL. The observed normalization also occurs in the currently pinned codec. This is a derived-identity defect; it does not merge the retained source declarations or remove embedded contents. A restoration check should preserve the single-slash control and the repeated-slash URL while retaining both candidates and their contents. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The released public CLI reports the wrong derived original-source identity for a bounded valid map, while retaining the raw source declaration and embedded text; downstream production damage was not observed. - Workaround status: available - Workaround basis: For the demonstrated absent-sourceRoot case, consumers can derive the correct URL from retained reported_source and url_context using standard URL parsing. The executed comparison produced the expected distinct URLs without changing the input.
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#4090
No description provided.