[GH-ISSUE #3883] [Bug] Computed module-table slot write still reports replaced Electron BrowserWindow #1503

Closed
opened 2026-10-16 21:06:15 +00:00 by bsakaguchi · 1 comment
Owner

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

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 reports an Electron BrowserWindow and its contextIsolation: false preference even though the module-table slot that would supply that factory is overwritten before use by a local factory, when the overwriting index is a constant identifier (load.m[key]) rather than a literal (load.m[0]).

The CLI reports an Electron BrowserWindow and its explicit contextIsolation: false preference after load.m[key] unconditionally replaces the Electron factory with a factory exporting a local class.

The equivalent literal load.m[0] replacement correctly suppresses the BrowserWindow fact.

The computed variant's BrowserWindow graph observation carries authority: "ast-static-analysis", state: "observed", confidence: "exact", and complete observation coverage, and all three analyses succeed.

The replacement happens before the only load(0) invocation, so the original Electron factory cannot supply the class used at the reported construction.

Reproduction

Point REA_ROOT at a built source checkout (the probe invokes the CLI through scripts/rea.mjs). The script creates only inert local JavaScript fixtures and runs the real CLI sequentially; no Electron runtime is needed and REA never executes the fixtures.

FIXTURES="$(mktemp -d)"
python3 - "$REA_ROOT" "$FIXTURES" <<'PY'
import json, pathlib, subprocess, sys
repo, work = map(pathlib.Path, sys.argv[1:])
prefix = '''(() => {
  const table = {0: module => { module.exports = require("electron"); }};
  function load(key) {
    const module = {exports: {}};
    table[key](module, module.exports, load);
    return module.exports;
  }
  load.m = table;
'''
suffix = '''
  const api = load(0);
  new api.BrowserWindow({webPreferences: {contextIsolation: false}});
})();
'''
mutations = {
  'intact': '',
  'literal': 'load.m[0] = module => { module.exports = {BrowserWindow: class {}}; };',
  'computed': 'const key = 0; load.m[key] = module => { module.exports = {BrowserWindow: class {}}; };',
}
for variant, mutation in mutations.items():
    directory = work / variant
    directory.mkdir(parents=True)
    (directory / 'bundle.js').write_text(prefix + mutation + suffix)
    result = subprocess.run(['nice', '-n', '10', 'node', str(repo / 'scripts/rea.mjs'),
        'analyze-javascript-application', str(directory), '--format', 'json'],
        capture_output=True, text=True, timeout=60, check=True)
    value = json.loads(result.stdout)['normalized_result']
    print(json.dumps({'variant': variant, 'browser_windows': value['summary']['browser_windows']}), flush=True)
PY

Expected behavior

docs/javascript-artifact-reconstruction.md:345 requires proven lexical module aliases for Electron API identity, and docs/javascript-artifact-reconstruction.md:361 states that reassignments, dynamic selections, and visibly overwritten namespace members remain unresolved.

The loader proof contract at src/domain/javascript/javascriptBootstrapIntegrity.ts:31 requires the loader to return the exports of the factory declared at the selected table slot, and to return no proof when that is unproven.

A visible write that replaces that slot with a local factory must invalidate the former Electron identity, including when the index comes from a constant such as const key = 0.

Impact and scope

static Electron boundary inventories can attribute BrowserWindow constructions and explicit preferences to a local replacement class, but the effect is bounded to a recognized .m module-table bootstrap whose slot is overwritten through a computed nonliteral index.

Workaround: none. no caller option preserving the original artifact corrects the returned graph; a literal-index source rewrite changes the supplied artifact and only serves as a discriminating control.

Reporter's impact estimate: P2 (supported).

REA version

6.5.0, built from source at main commit 034342ba28 (latest npm release is 6.5.0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64

Target and artifact context

JavaScript application directory (Webpack-style module-table bundle)

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • Expected source: docs/javascript-artifact-reconstruction.md:345 and docs/javascript-artifact-reconstruction.md:361; loader proof contract at src/domain/javascript/javascriptBootstrapIntegrity.ts:31
  • Failure source: observed browser_windows: 1 for the computed-key variant (output below); static cause at src/domain/javascript/javascriptBootstrapIntegrity.ts:517 passing a nonliteral index to argumentValue, which accepts only numeric or string literals per src/domain/javascript/javascriptStaticAnalysisHelpers.ts:716
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete
{"variant": "intact", "browser_windows": 1}
{"variant": "literal", "browser_windows": 0}
{"variant": "computed", "browser_windows": 1}

Typed outcome or recovery expected

The computed-key variant should report browser_windows: 0 (matching the literal-index control), with no proven Electron BrowserWindow observation attributed to the local replacement factory.

Restoration check:

Failing: the computed-key variant reports browser_windows: 1 with a BrowserWindow observation attributed to the local replacement class.

Passing: the same computed-key input reports browser_windows: 0, matching the literal load.m[0] control, while the intact variant still reports browser_windows: 1.

Analysis or suspected cause

javascriptBootstrapIntegrity.ts:517 passes a nonliteral property index to argumentValue (javascriptStaticAnalysisHelpers.ts:716), which accepts only numeric or string literals; the identifier index yields no replacement key, the proof still succeeds at javascriptBootstrapIntegrity.ts:426, and javascriptBundledRequireOrigin.ts:382 retains the original require("electron") external for slot 0.

Additional context

Source-derived cause: src/domain/javascript/javascriptBootstrapIntegrity.ts:465 accepts an indexed load.m reference as contained; src/domain/javascript/javascriptBootstrapIntegrity.ts:517 passes the property expression to argumentValue, which only accepts literal keys, so an identifier index produces no replacement key and the proof still succeeds at src/domain/javascript/javascriptBootstrapIntegrity.ts:426; src/domain/javascript/javascriptBundledRequireOrigin.ts:382 then retains the original require("electron") external for slot 0. A larger two-module fixture also reproduced the discrepancy, including with Number("45840") as the replacement key.

Related issues: open #3315 concerns replacement or deletion of the .n default-export getter (this report overwrites the .m module-table slot and uses no getter); closed #2932 added recovery through intact Webpack registries and does not cover this invalidation failure. Not an exact duplicate of either.

This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3883 ### 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` reports an Electron BrowserWindow and its `contextIsolation: false` preference even though the module-table slot that would supply that factory is overwritten before use by a local factory, when the overwriting index is a constant identifier (`load.m[key]`) rather than a literal (`load.m[0]`). The CLI reports an Electron BrowserWindow and its explicit `contextIsolation: false` preference after `load.m[key]` unconditionally replaces the Electron factory with a factory exporting a local class. The equivalent literal `load.m[0]` replacement correctly suppresses the BrowserWindow fact. The computed variant's BrowserWindow graph observation carries `authority: "ast-static-analysis"`, `state: "observed"`, `confidence: "exact"`, and complete observation coverage, and all three analyses succeed. The replacement happens before the only `load(0)` invocation, so the original Electron factory cannot supply the class used at the reported construction. ### Reproduction Point `REA_ROOT` at a built source checkout (the probe invokes the CLI through `scripts/rea.mjs`). The script creates only inert local JavaScript fixtures and runs the real CLI sequentially; no Electron runtime is needed and REA never executes the fixtures. ```sh FIXTURES="$(mktemp -d)" python3 - "$REA_ROOT" "$FIXTURES" <<'PY' import json, pathlib, subprocess, sys repo, work = map(pathlib.Path, sys.argv[1:]) prefix = '''(() => { const table = {0: module => { module.exports = require("electron"); }}; function load(key) { const module = {exports: {}}; table[key](module, module.exports, load); return module.exports; } load.m = table; ''' suffix = ''' const api = load(0); new api.BrowserWindow({webPreferences: {contextIsolation: false}}); })(); ''' mutations = { 'intact': '', 'literal': 'load.m[0] = module => { module.exports = {BrowserWindow: class {}}; };', 'computed': 'const key = 0; load.m[key] = module => { module.exports = {BrowserWindow: class {}}; };', } for variant, mutation in mutations.items(): directory = work / variant directory.mkdir(parents=True) (directory / 'bundle.js').write_text(prefix + mutation + suffix) result = subprocess.run(['nice', '-n', '10', 'node', str(repo / 'scripts/rea.mjs'), 'analyze-javascript-application', str(directory), '--format', 'json'], capture_output=True, text=True, timeout=60, check=True) value = json.loads(result.stdout)['normalized_result'] print(json.dumps({'variant': variant, 'browser_windows': value['summary']['browser_windows']}), flush=True) PY ``` ### Expected behavior docs/javascript-artifact-reconstruction.md:345 requires proven lexical module aliases for Electron API identity, and docs/javascript-artifact-reconstruction.md:361 states that reassignments, dynamic selections, and visibly overwritten namespace members remain unresolved. The loader proof contract at src/domain/javascript/javascriptBootstrapIntegrity.ts:31 requires the loader to return the exports of the factory declared at the selected table slot, and to return no proof when that is unproven. A visible write that replaces that slot with a local factory must invalidate the former Electron identity, including when the index comes from a constant such as `const key = 0`. ### Impact and scope static Electron boundary inventories can attribute BrowserWindow constructions and explicit preferences to a local replacement class, but the effect is bounded to a recognized `.m` module-table bootstrap whose slot is overwritten through a computed nonliteral index. Workaround: none. no caller option preserving the original artifact corrects the returned graph; a literal-index source rewrite changes the supplied artifact and only serves as a discriminating control. Reporter's impact estimate: P2 (supported). ### REA version 6.5.0, built from source at main commit 034342ba28c8e56c3746be92b78aa7299afa02bd (latest npm release is 6.5.0) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64 ### Target and artifact context JavaScript application directory (Webpack-style module-table bundle) ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence - Expected source: docs/javascript-artifact-reconstruction.md:345 and docs/javascript-artifact-reconstruction.md:361; loader proof contract at src/domain/javascript/javascriptBootstrapIntegrity.ts:31 - Failure source: observed `browser_windows: 1` for the computed-key variant (output below); static cause at src/domain/javascript/javascriptBootstrapIntegrity.ts:517 passing a nonliteral index to `argumentValue`, which accepts only numeric or string literals per src/domain/javascript/javascriptStaticAnalysisHelpers.ts:716 - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete ```text {"variant": "intact", "browser_windows": 1} {"variant": "literal", "browser_windows": 0} {"variant": "computed", "browser_windows": 1} ``` ### Typed outcome or recovery expected The computed-key variant should report browser_windows: 0 (matching the literal-index control), with no proven Electron BrowserWindow observation attributed to the local replacement factory. Restoration check: Failing: the computed-key variant reports `browser_windows: 1` with a BrowserWindow observation attributed to the local replacement class. Passing: the same computed-key input reports `browser_windows: 0`, matching the literal `load.m[0]` control, while the intact variant still reports `browser_windows: 1`. ### Analysis or suspected cause javascriptBootstrapIntegrity.ts:517 passes a nonliteral property index to argumentValue (javascriptStaticAnalysisHelpers.ts:716), which accepts only numeric or string literals; the identifier index yields no replacement key, the proof still succeeds at javascriptBootstrapIntegrity.ts:426, and javascriptBundledRequireOrigin.ts:382 retains the original require("electron") external for slot 0. ### Additional context Source-derived cause: src/domain/javascript/javascriptBootstrapIntegrity.ts:465 accepts an indexed `load.m` reference as contained; src/domain/javascript/javascriptBootstrapIntegrity.ts:517 passes the property expression to `argumentValue`, which only accepts literal keys, so an identifier index produces no replacement key and the proof still succeeds at src/domain/javascript/javascriptBootstrapIntegrity.ts:426; src/domain/javascript/javascriptBundledRequireOrigin.ts:382 then retains the original `require("electron")` external for slot 0. A larger two-module fixture also reproduced the discrepancy, including with `Number("45840")` as the replacement key. Related issues: open #3315 concerns replacement or deletion of the `.n` default-export getter (this report overwrites the `.m` module-table slot and uses no getter); closed #2932 added recovery through intact Webpack registries and does not cover this invalidation failure. Not an exact duplicate of either. This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

The module-table proof now evaluates computed slot keys through the semantic expression owner, records resolved numeric/string keys as replaced, and refuses unresolved computed writes rather than preserving all prior slot identities. The regression overwrites the actual Electron slot using a constant identifier, deletes it through a constant string, tests unknown keys, and preserves a distinct slot control. BrowserWindow count must be zero for the changed/unknown slot and one only for the unrelated slot.

Implementation: javascriptBootstrapIntegrity.ts, electronBundledRequireIdentity.test.ts.

The relevant regression checks in electronBundledRequireIdentity.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380784287 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. The module-table proof now evaluates computed slot keys through the semantic expression owner, records resolved numeric/string keys as replaced, and refuses unresolved computed writes rather than preserving all prior slot identities. The regression overwrites the actual Electron slot using a constant identifier, deletes it through a constant string, tests unknown keys, and preserves a distinct slot control. BrowserWindow count must be zero for the changed/unknown slot and one only for the unrelated slot. Implementation: [javascriptBootstrapIntegrity.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptBootstrapIntegrity.ts#L632), [electronBundledRequireIdentity.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/electronBundledRequireIdentity.test.ts#L40). The relevant regression checks in [electronBundledRequireIdentity.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/electronBundledRequireIdentity.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#1503
No description provided.