Summary
In src/core/file/truncateBase64.ts, dataUriPattern captures data-URI parameters with a repeated capturing group (;[a-zA-Z0-9\-=]+)*. JavaScript regex semantics keep only the last iteration of a repeated capture group, so a data URI with two or more parameters loses all but the final one when the match is rebuilt in the replace callback. The truncation feature silently corrupts such URIs in the packed output instead of only shortening the base64 payload.
Static-analysis finding based on reading main; not executed here.
Location
- File:
src/core/file/truncateBase64.ts
- Function:
truncateBase64Content (pattern at lines 9-12, callback at lines 55-58)
- Relevant code:
const dataUriPattern = new RegExp(
`data:([a-zA-Z0-9\\/\\-\\+]+)(;[a-zA-Z0-9\\-=]+)*;base64,([A-Za-z0-9+/=]{40,})`,
'g',
);
...
processedContent = processedContent.replace(dataUriPattern, (_match, mimeType, params, base64Data) => {
const preview = base64Data.substring(0, TRUNCATION_LENGTH);
return `data:${mimeType}${params || ''};base64,${preview}...`;
});
Problem
For input data:text/plain;a=1;b=2;base64,AAAA... (payload >= 40 chars):
- Group 1 (
mimeType) = text/plain.
- The repeated group matches twice: first iteration consumes
;a=1, second consumes ;b=2. Per ECMAScript semantics, the group's captured value after the match is the last iteration: ";b=2". ;a=1 is matched but never retained anywhere.
- The literal
;base64, then matches, followed by the payload in group 3.
- The callback rebuilds the URI as
data:text/plain;b=2;base64,<32 chars>... - ;a=1 has been deleted from the user's source content.
The function's contract (shorten long base64 payloads) implies everything outside the payload must be preserved byte-for-byte.
Trigger / Reproduction
Based on source reading:
- Run repomix with
truncateBase64: true on any repository containing a file with a data URI carrying two parameters, e.g. data:image/png;charset=utf-8;name=x.png;base64,iVBORw0KGgoAAA....
- In the output bundle the URI becomes
data:image/png;name=x.png;base64,... - the charset=utf-8 parameter is gone.
Expected Behavior
All parameters survive: data:image/png;charset=utf-8;name=x.png;base64,<truncated>....
Actual Behavior
Every parameter except the last is removed whenever more than one is present. Single-parameter URIs are unaffected, which explains why this has gone unnoticed.
Impact
Silent content corruption inside the generated pack: data URIs that consumers round-trip (e.g. embedded SVGs with charset + name, or URIs with vendor-specific flags) become invalid or semantically different after packing, with no warning. Since repomix output is often fed to LLMs/tools as ground truth, corrupted URIs propagate downstream.
Suggested Direction
Make the repeated group non-capturing - (?:;[a-zA-Z0-9\-=]+)* - and capture the full parameter run separately, e.g. wrap it: ((?:;[a-zA-Z0-9\-=]+)*). Then params holds every iteration concatenated, and no other change is needed.
Evidence
- ECMAScript spec (RegExp
[[Match]]): for a quantified capturing group, the final capture state is the one from the last successful iteration.
- The rebuild path uses only
mimeType, params, and base64Data; there is no other mechanism preserving the original prefix of the URI.
Summary
In
src/core/file/truncateBase64.ts,dataUriPatterncaptures data-URI parameters with a repeated capturing group(;[a-zA-Z0-9\-=]+)*. JavaScript regex semantics keep only the last iteration of a repeated capture group, so a data URI with two or more parameters loses all but the final one when the match is rebuilt in the replace callback. The truncation feature silently corrupts such URIs in the packed output instead of only shortening the base64 payload.Static-analysis finding based on reading
main; not executed here.Location
src/core/file/truncateBase64.tstruncateBase64Content(pattern at lines 9-12, callback at lines 55-58)Problem
For input
data:text/plain;a=1;b=2;base64,AAAA...(payload >= 40 chars):mimeType) =text/plain.;a=1, second consumes;b=2. Per ECMAScript semantics, the group's captured value after the match is the last iteration:";b=2".;a=1is matched but never retained anywhere.;base64,then matches, followed by the payload in group 3.data:text/plain;b=2;base64,<32 chars>...-;a=1has been deleted from the user's source content.The function's contract (shorten long base64 payloads) implies everything outside the payload must be preserved byte-for-byte.
Trigger / Reproduction
Based on source reading:
truncateBase64: trueon any repository containing a file with a data URI carrying two parameters, e.g.data:image/png;charset=utf-8;name=x.png;base64,iVBORw0KGgoAAA....data:image/png;name=x.png;base64,...- thecharset=utf-8parameter is gone.Expected Behavior
All parameters survive:
data:image/png;charset=utf-8;name=x.png;base64,<truncated>....Actual Behavior
Every parameter except the last is removed whenever more than one is present. Single-parameter URIs are unaffected, which explains why this has gone unnoticed.
Impact
Silent content corruption inside the generated pack: data URIs that consumers round-trip (e.g. embedded SVGs with
charset+name, or URIs with vendor-specific flags) become invalid or semantically different after packing, with no warning. Since repomix output is often fed to LLMs/tools as ground truth, corrupted URIs propagate downstream.Suggested Direction
Make the repeated group non-capturing -
(?:;[a-zA-Z0-9\-=]+)*- and capture the full parameter run separately, e.g. wrap it:((?:;[a-zA-Z0-9\-=]+)*). Thenparamsholds every iteration concatenated, and no other change is needed.Evidence
[[Match]]): for a quantified capturing group, the final capture state is the one from the last successful iteration.mimeType,params, andbase64Data; there is no other mechanism preserving the original prefix of the URI.