[GH-ISSUE #1380] [Bug] analyze-javascript-application aborts with RangeError on a large inline source map in a two-byte source #480

Closed
opened 2026-10-15 18:25:24 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @KadirJeager on GitHub (Oct 9, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1380

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

analyze-javascript-application aborts the whole analysis with execution_failure / RangeError: Maximum call stack size exceeded on the Heroic Games Launcher 2.22.3 app.asar (Arch package heroic-games-launcher, 31 MB ASAR).

The progress stream reaches Parsing and projecting build/main/main.js (183/1621) and then fails. build/main/main.js is a 13 MB bundle that ends with a single-line inline source map comment of 9,423,620 characters (//# sourceMappingURL=data:application/json;charset=utf-8;base64,...), and the file elsewhere contains non-Latin-1 characters.

The overflow happens inside RegExp.exec, not in REA recursion (the captured stack is only 7 frames deep):

RangeError: Maximum call stack size exceeded
    at RegExp.exec (<anonymous>)
    at addSourceMapDirectives (dist/domain/javascript/javascriptStaticAnalysisCalls.js:216:80)
    at dist/domain/javascript/javascriptStaticAnalysis.js:22:5
    at dist/application/javascript/JavaScriptArtifactAnalysis.js:89:22
    at dist/application/javascript/JavaScriptArtifactAnalysis.js:37:9
    at dist/application/javascript/JavaScriptArtifactReconstruction.js:33:26
    at dist/application/javascript/JavaScriptApplicationService.js:33:31

The same file run as a standalone directory (only main.js + a minimal package.json) fails identically.

Reproduction

Reduced, target-independent reproduction of the failing call (the regex is copied from addSourceMapDirectives in src/domain/javascript/javascriptStaticAnalysisCalls.ts):

// node repro.mjs
const re = /^(?:\/\/|\/\*)[#@]\s*sourceMappingURL\s*=\s*([^\s*]+)/u;
const comment = '//# sourceMappingURL=data:application/json;base64,' + 'A'.repeat(9_500_000);
const source = 'const label = "ğ";\n' + comment; // any non-Latin-1 char => V8 stores the source as two-byte
const start = source.indexOf('//#');
re.exec(source.slice(start)); // RangeError: Maximum call stack size exceeded
re.exec(comment);             // one-byte string of the same text: ok

Observed on Node v26.11.1 (V8 14.6.202.34-node.37):

  • two-byte slice: RangeError: Maximum call stack size exceeded
  • one-byte string with identical text: ok
  • a flattened copy of the two-byte slice (Buffer.from(s, 'utf8').toString()): ok
  • the two-byte failure threshold is about 8.4 million characters in the ([^\s*]+) capture

End-to-end reproduction with the real target:

  1. Install heroic-games-launcher 2.22.3 (or take resources/app.asar from the upstream 2.22.3 release).
  2. Run rea analyze-javascript-application /path/to/app.asar --json.
  3. Observe execution_failure with error_name: RangeError.

Expected behavior

A single oversized source-map directive should not abort analysis of the whole application. The directive should still be recorded, or skipped with a bounded limitation (for example a data: URL whose length exceeds a cap), while the other 1,620 files are analyzed.

Impact and scope

Any Electron/JS application whose bundles embed large inline (data:) source maps and contain at least one non-Latin-1 character can be unanalyzable. Webpack/Vite/esbuild builds with inline-source-map commonly produce this. Analysis is all-or-nothing, so one file blocks the whole graph. No evidence corruption: the run fails closed.

Not affected in my runs: Claude Desktop's app.asar (43 MB) and ChatGPT desktop's app.asar (538 MB) both completed.

REA version

6.2.0 (npm rea-agents@6.2.0). The same regex is still present on main at 08ec2bf.

Runtime and platform

Node v26.11.1 (V8 14.6.202.34-node.37); CachyOS (Arch) Linux x86_64

Target and artifact context

Electron ASAR, Heroic Games Launcher 2.22.3, build/main/main.js (13,221,069 bytes, 101,575 lines, last line = 9.4 MB inline base64 source map)

Interface

CLI

Earliest failure stage

Artifact or evidence registration

Analysis or suspected cause

Source-derived and confirmed only by the reduced repro above: addSourceMapDirectives runs /^(?:\/\/|\/\*)[#@]\s*sourceMappingURL\s*=\s*([^\s*]+)/u over source.slice(start, end) of each comment. For a sliced two-byte string, V8's irregexp exhausts its backtrack stack while the greedy ([^\s*]+) consumes several million characters and throws RangeError. That error is not caught per file, so it surfaces as a provider execution_failure.

A possible direction (not tested against the test suite): match only the bounded prefix ^(?:\/\/|\/\*)[#@]\s*sourceMappingURL\s*=\s* with the regex, then find the end of the URL with a linear scan for whitespace/*. Alternatively, cap or summarize data: URLs before they are captured. A regression fixture could be a comment longer than 8.4M characters in a source containing one non-Latin-1 character.

Originally created by @KadirJeager on GitHub (Oct 9, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1380 ### 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 `analyze-javascript-application` aborts the whole analysis with `execution_failure` / `RangeError: Maximum call stack size exceeded` on the Heroic Games Launcher 2.22.3 `app.asar` (Arch package `heroic-games-launcher`, 31 MB ASAR). The progress stream reaches `Parsing and projecting build/main/main.js (183/1621)` and then fails. `build/main/main.js` is a 13 MB bundle that ends with a single-line inline source map comment of 9,423,620 characters (`//# sourceMappingURL=data:application/json;charset=utf-8;base64,...`), and the file elsewhere contains non-Latin-1 characters. The overflow happens inside `RegExp.exec`, not in REA recursion (the captured stack is only 7 frames deep): ```text RangeError: Maximum call stack size exceeded at RegExp.exec (<anonymous>) at addSourceMapDirectives (dist/domain/javascript/javascriptStaticAnalysisCalls.js:216:80) at dist/domain/javascript/javascriptStaticAnalysis.js:22:5 at dist/application/javascript/JavaScriptArtifactAnalysis.js:89:22 at dist/application/javascript/JavaScriptArtifactAnalysis.js:37:9 at dist/application/javascript/JavaScriptArtifactReconstruction.js:33:26 at dist/application/javascript/JavaScriptApplicationService.js:33:31 ``` The same file run as a standalone directory (only `main.js` + a minimal `package.json`) fails identically. ### Reproduction Reduced, target-independent reproduction of the failing call (the regex is copied from `addSourceMapDirectives` in `src/domain/javascript/javascriptStaticAnalysisCalls.ts`): ```js // node repro.mjs const re = /^(?:\/\/|\/\*)[#@]\s*sourceMappingURL\s*=\s*([^\s*]+)/u; const comment = '//# sourceMappingURL=data:application/json;base64,' + 'A'.repeat(9_500_000); const source = 'const label = "ğ";\n' + comment; // any non-Latin-1 char => V8 stores the source as two-byte const start = source.indexOf('//#'); re.exec(source.slice(start)); // RangeError: Maximum call stack size exceeded re.exec(comment); // one-byte string of the same text: ok ``` Observed on Node v26.11.1 (V8 14.6.202.34-node.37): - two-byte slice: `RangeError: Maximum call stack size exceeded` - one-byte string with identical text: ok - a flattened copy of the two-byte slice (`Buffer.from(s, 'utf8').toString()`): ok - the two-byte failure threshold is about 8.4 million characters in the `([^\s*]+)` capture End-to-end reproduction with the real target: 1. Install `heroic-games-launcher` 2.22.3 (or take `resources/app.asar` from the upstream 2.22.3 release). 2. Run `rea analyze-javascript-application /path/to/app.asar --json`. 3. Observe `execution_failure` with `error_name: RangeError`. ### Expected behavior A single oversized source-map directive should not abort analysis of the whole application. The directive should still be recorded, or skipped with a bounded limitation (for example a `data:` URL whose length exceeds a cap), while the other 1,620 files are analyzed. ### Impact and scope Any Electron/JS application whose bundles embed large inline (`data:`) source maps and contain at least one non-Latin-1 character can be unanalyzable. Webpack/Vite/esbuild builds with `inline-source-map` commonly produce this. Analysis is all-or-nothing, so one file blocks the whole graph. No evidence corruption: the run fails closed. Not affected in my runs: Claude Desktop's `app.asar` (43 MB) and ChatGPT desktop's `app.asar` (538 MB) both completed. ### REA version 6.2.0 (npm `rea-agents@6.2.0`). The same regex is still present on `main` at 08ec2bf. ### Runtime and platform Node v26.11.1 (V8 14.6.202.34-node.37); CachyOS (Arch) Linux x86_64 ### Target and artifact context Electron ASAR, Heroic Games Launcher 2.22.3, `build/main/main.js` (13,221,069 bytes, 101,575 lines, last line = 9.4 MB inline base64 source map) ### Interface CLI ### Earliest failure stage Artifact or evidence registration ### Analysis or suspected cause Source-derived and confirmed only by the reduced repro above: `addSourceMapDirectives` runs `/^(?:\/\/|\/\*)[#@]\s*sourceMappingURL\s*=\s*([^\s*]+)/u` over `source.slice(start, end)` of each comment. For a sliced two-byte string, V8's irregexp exhausts its backtrack stack while the greedy `([^\s*]+)` consumes several million characters and throws `RangeError`. That error is not caught per file, so it surfaces as a provider `execution_failure`. A possible direction (not tested against the test suite): match only the bounded prefix `^(?:\/\/|\/\*)[#@]\s*sourceMappingURL\s*=\s*` with the regex, then find the end of the URL with a linear scan for whitespace/`*`. Alternatively, cap or summarize `data:` URLs before they are captured. A regression fixture could be a comment longer than 8.4M characters in a source containing one non-Latin-1 character.
bsakaguchi 2026-10-15 18:25:24 +00:00
Author
Owner

@CassieFox23 commented on GitHub (Oct 9, 2026):

TL;DR: I can reproduce this on Linux. It fails on Node 22 and Node 24. It fails on 6.2.0 and on current main. A small map or an ASCII-only file is fine.

Reduced repro

I ran your script as written. Results match on both Node versions:

case Node 22.23.1 Node 24.18.0
two-byte slice RangeError RangeError
one-byte string ok ok
flattened copy ok ok

I bisected the capture length where it starts to fail:

Node last ok first fail
22.23.1 8,388,572 8,388,573
24.18.0 8,388,588 8,388,589

Both sit just under 2^23 (8,388,608). That fits your "about 8.4 million".

End to end

I built three tiny apps. Each has a package.json and one main.js. The last line is an inline base64 source map. The big map is 9,600,124 characters.

app 6.2.0 main
one ğ, big map exit 1 exit 1
ASCII only, big map exit 0 exit 0
one ğ, small map exit 0 exit 0

The table holds on both Node versions. Each failure is execution_failure with RangeError: Maximum call stack size exceeded. Progress stops at "Parsing and projecting main.js (1/2)".

Two side notes

  • The prefix-only regex idea still throws in one case. I tried ^(?:\/\/|\/\*)[#@]\s*sourceMappingURL\s*=\s* alone. It throws the same RangeError on both Nodes. The input was a two-byte comment with 9.5 million spaces in a \s* slot. Real bundles won't look like that. A hostile file could.
  • The ASCII run keeps the whole data URL in the output. The JSON is 38 MB on 6.2.0 and 9.7 MB on main. That may deserve its own cap.

I did not test Node 26.

Test setup
  • Box: Nobara 44 (Fedora 44 base), x86-64
  • Node: 22.23.1, 24.18.0
  • Main: 08ec2bf8

🤖 Generated with Claude Code (Opus 5.5) 🦊

<!-- gh-comment-id:6091253226 --> @CassieFox23 commented on GitHub (Oct 9, 2026): **TL;DR:** I can reproduce this on Linux. It fails on Node 22 and Node 24. It fails on 6.2.0 and on current main. A small map or an ASCII-only file is fine. ### Reduced repro I ran your script as written. Results match on both Node versions: | case | Node 22.23.1 | Node 24.18.0 | |---|---|---| | two-byte slice | RangeError | RangeError | | one-byte string | ok | ok | | flattened copy | ok | ok | I bisected the capture length where it starts to fail: | Node | last ok | first fail | |---|---|---| | 22.23.1 | 8,388,572 | 8,388,573 | | 24.18.0 | 8,388,588 | 8,388,589 | Both sit just under 2^23 (8,388,608). That fits your "about 8.4 million". ### End to end I built three tiny apps. Each has a `package.json` and one `main.js`. The last line is an inline base64 source map. The big map is 9,600,124 characters. | app | 6.2.0 | main | |---|---|---| | one `ğ`, big map | exit 1 | exit 1 | | ASCII only, big map | exit 0 | exit 0 | | one `ğ`, small map | exit 0 | exit 0 | The table holds on both Node versions. Each failure is `execution_failure` with `RangeError: Maximum call stack size exceeded`. Progress stops at "Parsing and projecting main.js (1/2)". ### Two side notes - The prefix-only regex idea still throws in one case. I tried `^(?:\/\/|\/\*)[#@]\s*sourceMappingURL\s*=\s*` alone. It throws the same RangeError on both Nodes. The input was a two-byte comment with 9.5 million spaces in a `\s*` slot. Real bundles won't look like that. A hostile file could. - The ASCII run keeps the whole data URL in the output. The JSON is 38 MB on 6.2.0 and 9.7 MB on main. That may deserve its own cap. I did not test Node 26. <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), x86-64 - Node: 22.23.1, 24.18.0 - Main: `08ec2bf8` </details> 🤖 Generated with Claude Code (Opus 5.5) 🦊
Author
Owner

@CassieFox23 commented on GitHub (Oct 10, 2026):

TL;DR: Node 26 still has the bug. The limit sits in the same spot. One thing changed: long runs of spaces no longer throw.

Earlier I said Node 26 was not run. Here it is, on Node 26.11.1.

check 22.23.1 24.18.0 26.11.1
first failing capture 8,388,573 8,388,589 8,388,586
two-byte file, big map exit 1 exit 1 exit 1
prefix regex, 9.5M spaces throws throws ok

On 26 I ran release 6.3.0 and main at 7aa4d768. Both fail the same way. ASCII files and small maps still pass. The prefix regex passed with 9.5M spaces in each \s* slot. Node 24 still throws on all three.

🤖 Generated with Claude Code (Opus 5.5) 🦊

<!-- gh-comment-id:6092319371 --> @CassieFox23 commented on GitHub (Oct 10, 2026): **TL;DR:** Node 26 still has the bug. The limit sits in the same spot. One thing changed: long runs of spaces no longer throw. Earlier I said Node 26 was not run. Here it is, on Node 26.11.1. | check | 22.23.1 | 24.18.0 | 26.11.1 | |---|---|---|---| | first failing capture | 8,388,573 | 8,388,589 | 8,388,586 | | two-byte file, big map | exit 1 | exit 1 | exit 1 | | prefix regex, 9.5M spaces | throws | throws | ok | On 26 I ran release 6.3.0 and main at `7aa4d768`. Both fail the same way. ASCII files and small maps still pass. The prefix regex passed with 9.5M spaces in each `\s*` slot. Node 24 still throws on all three. 🤖 Generated with Claude Code (Opus 5.5) 🦊
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#480
No description provided.