[GH-ISSUE #3962] [Bug] Unrelated join and resolve methods produce false Electron preload declarations #1569

Closed
opened 2026-10-16 21:07:15 +00:00 by bsakaguchi · 2 comments
Owner

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

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

Static application reconstruction reports incorrect literal preload paths when an Electron BrowserWindow option calls a custom resolve method or an array's join method. The returned application graph includes an inferred preload node and a BrowserWindow loads edge for each fabricated value. This can misdirect an analyst who follows the recovered entrypoint.

Reproduction

Use the baseline checkout and runtime below with dependencies installed and runtime artifacts already built. From the repository root, run this self-contained reproduction. It creates disposable extracted applications and an isolated HOME, invokes the supported CLI without executing the applications, and prints the relevant graph values.

python3 - <<'PYCODE'
import json
import os
from pathlib import Path
import subprocess
import tempfile

cases = {
    "custom-resolve": (
        'const settings = { resolve: (...parts) => parts.join("/") };\n'
        'new BrowserWindow({ webPreferences: { preload: settings.resolve("a", "/b") } });\n'
    ),
    "array-join": (
        'new BrowserWindow({ webPreferences: { preload: ["a", "b"].join("/") } });\n'
    ),
    "path-control": (
        'const path = require("node:path");\n'
        'new BrowserWindow({ webPreferences: { preload: path.join(__dirname, "preload.js") } });\n'
        'new BrowserWindow({ webPreferences: { preload: path.resolve("/a", "b") } });\n'
    ),
}
with tempfile.TemporaryDirectory(prefix="rea-path-verification-") as directory:
    root = Path(directory)
    home = root / "home"
    home.mkdir()
    for name, source in cases.items():
        app = root / name
        app.mkdir()
        (app / "main.js").write_text(
            'const { BrowserWindow } = require("electron");\n' + source
        )
        result = subprocess.run(
            ["node", "scripts/rea.mjs", "analyze-javascript-application",
             str(app), "--format", "json"],
            env={**os.environ, "HOME": str(home)},
            capture_output=True, text=True, timeout=45, check=True,
        )
        graph = json.loads(result.stdout)["normalized_result"]["graph"]
        windows = [
            observation["properties"]
            for node in graph["nodes"]
            for observation in node["observations"]
            if "preload_path" in observation["properties"]
        ]
        print(json.dumps({
            "case": name,
            "exit_code": result.returncode,
            "preload_paths": sorted(window["preload_path"] for window in windows),
            "preference_statuses": sorted(
                preference["value"]["status"]
                for window in windows for preference in window["web_preferences"]
                if preference["name"] == "preload"
            ),
            "preload_nodes": sum(node["kind"] == "electron-preload"
                                 for node in graph["nodes"]),
            "window_loads": sorted(
                edge["properties"]["declared_path"] for edge in graph["edges"]
                if edge["relation"] == "loads" and "declared_path" in edge["properties"]
            ),
        }))
PYCODE

Expected behavior

Reconstruction parses application code as inert data, recovers statically resolvable entrypoints, and preserves unresolved expressions as unknown. A method name alone cannot establish filesystem path semantics. The custom method and array method must not become exact /b and / preload declarations. If their actual string results are unsupported, the analyzer should retain the expression and an unknown preload path instead of creating those false entrypoint relationships. Accurate static evaluation of their actual semantics would also satisfy this contract.

Impact and scope

Two concrete static expression variants produced wrong literal preload declarations and high-confidence inferred relationships. The observed effect is bounded evidence corruption in synthetic application graphs; incorrect file execution, user-data loss, and real-application prevalence were not established.

Workaround: Manual comparison with the small fixture source exposes the false declarations, but no supported option that returns a corrected application graph while retaining these inputs was established.

Assessment: P2.

REA version

rea 6.5.0 at source commit 4692bb3330

Runtime and platform

Node 24.18.0; macOS arm64

Target and artifact context

Synthetic extracted application directories containing one inert JavaScript file each. No Electron installation, application execution or live provider was used.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The commands completed successfully with exit code 0 and returned these observed values:

{"case": "custom-resolve", "exit_code": 0, "preload_paths": ["/b"], "preference_statuses": ["literal"], "preload_nodes": 1, "window_loads": ["/b"]}
{"case": "array-join", "exit_code": 0, "preload_paths": ["/"], "preference_statuses": ["literal"], "preload_nodes": 1, "window_loads": ["/"]}
{"case": "path-control", "exit_code": 0, "preload_paths": ["/a/b", "preload.js"], "preference_statuses": ["literal", "literal"], "preload_nodes": 2, "window_loads": ["/a/b", "preload.js"]}

The failing cases also had filesystem-expression resolution context and high-confidence static relationship inference on their preload nodes. Those nodes reported resolution_status: "not-found" and null resolved paths. The defect fabricates declarations and relationships; these results do not establish successful file resolution or target execution. Their application graph coverage was complete even though the separate semantic graph retained unknowns.

Under ordinary JavaScript string semantics, the supplied custom method returns a//b and the array join returns a/b. That comparison follows from the fixture source; the analyzed applications were not executed. The genuine path controls produced the expected source-relative preload.js and absolute /a/b values.

Typed outcome or recovery expected

Rerun both failing fixtures and confirm that /b and / no longer appear as exact preload declarations or produce the corresponding false preload nodes and BrowserWindow loads edges. If the expressions remain unsupported, their preferences retain dynamic expressions and their preload paths remain null. If the analyzer resolves them, the results must follow the actual string semantics with appropriate provenance. Keep the genuine path controls returning preload.js and /a/b; simply disabling all call-expression path inference would lose supported behavior.

Additional context

Freshness checked against origin/main at 22ec8bdb2f. The cited failing implementation files are unchanged from the reproduced baseline; this is a source freshness check, not a runtime rerun on that revision.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3962 ### 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 Static application reconstruction reports incorrect literal preload paths when an Electron BrowserWindow option calls a custom `resolve` method or an array's `join` method. The returned application graph includes an inferred preload node and a BrowserWindow `loads` edge for each fabricated value. This can misdirect an analyst who follows the recovered entrypoint. ### Reproduction Use the baseline checkout and runtime below with dependencies installed and runtime artifacts already built. From the repository root, run this self-contained reproduction. It creates disposable extracted applications and an isolated HOME, invokes the supported CLI without executing the applications, and prints the relevant graph values. ```sh python3 - <<'PYCODE' import json import os from pathlib import Path import subprocess import tempfile cases = { "custom-resolve": ( 'const settings = { resolve: (...parts) => parts.join("/") };\n' 'new BrowserWindow({ webPreferences: { preload: settings.resolve("a", "/b") } });\n' ), "array-join": ( 'new BrowserWindow({ webPreferences: { preload: ["a", "b"].join("/") } });\n' ), "path-control": ( 'const path = require("node:path");\n' 'new BrowserWindow({ webPreferences: { preload: path.join(__dirname, "preload.js") } });\n' 'new BrowserWindow({ webPreferences: { preload: path.resolve("/a", "b") } });\n' ), } with tempfile.TemporaryDirectory(prefix="rea-path-verification-") as directory: root = Path(directory) home = root / "home" home.mkdir() for name, source in cases.items(): app = root / name app.mkdir() (app / "main.js").write_text( 'const { BrowserWindow } = require("electron");\n' + source ) result = subprocess.run( ["node", "scripts/rea.mjs", "analyze-javascript-application", str(app), "--format", "json"], env={**os.environ, "HOME": str(home)}, capture_output=True, text=True, timeout=45, check=True, ) graph = json.loads(result.stdout)["normalized_result"]["graph"] windows = [ observation["properties"] for node in graph["nodes"] for observation in node["observations"] if "preload_path" in observation["properties"] ] print(json.dumps({ "case": name, "exit_code": result.returncode, "preload_paths": sorted(window["preload_path"] for window in windows), "preference_statuses": sorted( preference["value"]["status"] for window in windows for preference in window["web_preferences"] if preference["name"] == "preload" ), "preload_nodes": sum(node["kind"] == "electron-preload" for node in graph["nodes"]), "window_loads": sorted( edge["properties"]["declared_path"] for edge in graph["edges"] if edge["relation"] == "loads" and "declared_path" in edge["properties"] ), })) PYCODE ``` ### Expected behavior Reconstruction parses application code as inert data, recovers statically resolvable entrypoints, and preserves unresolved expressions as unknown. A method name alone cannot establish filesystem path semantics. The custom method and array method must not become exact `/b` and `/` preload declarations. If their actual string results are unsupported, the analyzer should retain the expression and an unknown preload path instead of creating those false entrypoint relationships. Accurate static evaluation of their actual semantics would also satisfy this contract. ### Impact and scope Two concrete static expression variants produced wrong literal preload declarations and high-confidence inferred relationships. The observed effect is bounded evidence corruption in synthetic application graphs; incorrect file execution, user-data loss, and real-application prevalence were not established. Workaround: Manual comparison with the small fixture source exposes the false declarations, but no supported option that returns a corrected application graph while retaining these inputs was established. Assessment: P2. ### REA version rea 6.5.0 at source commit 4692bb33300c7cb77b095b6cc0c33da861c1b860 ### Runtime and platform Node 24.18.0; macOS arm64 ### Target and artifact context Synthetic extracted application directories containing one inert JavaScript file each. No Electron installation, application execution or live provider was used. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The commands completed successfully with exit code 0 and returned these observed values: ```json {"case": "custom-resolve", "exit_code": 0, "preload_paths": ["/b"], "preference_statuses": ["literal"], "preload_nodes": 1, "window_loads": ["/b"]} {"case": "array-join", "exit_code": 0, "preload_paths": ["/"], "preference_statuses": ["literal"], "preload_nodes": 1, "window_loads": ["/"]} {"case": "path-control", "exit_code": 0, "preload_paths": ["/a/b", "preload.js"], "preference_statuses": ["literal", "literal"], "preload_nodes": 2, "window_loads": ["/a/b", "preload.js"]} ``` The failing cases also had filesystem-expression resolution context and high-confidence static relationship inference on their preload nodes. Those nodes reported `resolution_status: "not-found"` and null resolved paths. The defect fabricates declarations and relationships; these results do not establish successful file resolution or target execution. Their application graph coverage was complete even though the separate semantic graph retained unknowns. Under ordinary JavaScript string semantics, the supplied custom method returns `a//b` and the array join returns `a/b`. That comparison follows from the fixture source; the analyzed applications were not executed. The genuine path controls produced the expected source-relative `preload.js` and absolute `/a/b` values. - Expected source: [docs/javascript-artifact-reconstruction.md:217-235](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/docs/javascript-artifact-reconstruction.md#L217) documents inert parsing and statically resolvable Electron preload entrypoints; lines 310-330 distinguish syntax observations from inferred relationships and preserve unknown values. [src/domain/javascript/javascriptStaticAnalysisHelpers.ts:181-182](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/domain/javascript/javascriptStaticAnalysisHelpers.ts#L181) limits path resolution to inert literal syntax. [src/domain/javascript/javascriptStaticPathResolution.test.ts:131-164](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/domain/javascript/javascriptStaticPathResolution.test.ts#L131) preserves unsupported expressions as dynamic with null preload paths. - Failure source: Observed CLI Evidence at `normalized_result.graph.nodes[].observations[].properties.preload_path` and `normalized_result.graph.edges[]` with `relation: "loads"`, reproduced by the command above. Source-derived explanation is at [src/domain/javascript/javascriptStaticAnalysisHelpers.ts:217-245](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/domain/javascript/javascriptStaticAnalysisHelpers.ts#L217) and `:673-690`: call selection uses method-name suffixes, and a non-identifier array receiver becomes bare `join`. [src/domain/javascript/electronStaticAnalysisBrowser.ts:96-149](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/domain/javascript/electronStaticAnalysisBrowser.ts#L96) promotes these results to literal preferences, and [src/application/javascript/ElectronBoundaryGraphWindows.ts:129-177](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/application/javascript/ElectronBoundaryGraphWindows.ts#L129) creates the preload node and loads edge. - Focused baseline check: `npm run test:local -- src/domain/javascript/javascriptStaticPathResolution.test.ts` passed all 64 existing tests. Those tests preserve genuine path behavior but do not cover these unrelated receiver cases. ### Typed outcome or recovery expected Rerun both failing fixtures and confirm that `/b` and `/` no longer appear as exact preload declarations or produce the corresponding false preload nodes and BrowserWindow loads edges. If the expressions remain unsupported, their preferences retain dynamic expressions and their preload paths remain null. If the analyzer resolves them, the results must follow the actual string semantics with appropriate provenance. Keep the genuine path controls returning `preload.js` and `/a/b`; simply disabling all call-expression path inference would lose supported behavior. ### Additional context Freshness checked against origin/main at 22ec8bdb2f7d4626587f23b1eed4a207df600386. The cited failing implementation files are unchanged from the reproduced baseline; this is a source freshness check, not a runtime rerun on that revision.
Author
Owner

@maxiaochaos commented on GitHub (Oct 15, 2026):

I'll take this — stop treating unrelated join/resolve as path preload. PR in a day or two.

<!-- gh-comment-id:6186288253 --> @maxiaochaos commented on GitHub (Oct 15, 2026): I'll take this — stop treating unrelated join/resolve as path preload. PR in a day or two.
Author
Owner

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

Checked the reported fix on main at d87e73f2db.

Static path evaluation no longer selects calls by join/resolve method-name suffix. It requires a callee query proving module provenance and an unmodified native method from path/path-posix. The local settings.resolve object and array join receiver lack that provenance and return undefined.

Implementation: javascriptStaticPathIntrinsics.ts, javascriptStaticAnalysisHelpers.ts.

The relevant regression checks in javascriptStaticApiIdentity.test.ts, javascriptStaticPathResolution.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:6380701715 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. Static path evaluation no longer selects calls by join/resolve method-name suffix. It requires a callee query proving module provenance and an unmodified native method from path/path-posix. The local settings.resolve object and array join receiver lack that provenance and return undefined. Implementation: [javascriptStaticPathIntrinsics.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptStaticPathIntrinsics.ts#L24), [javascriptStaticAnalysisHelpers.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptStaticAnalysisHelpers.ts#L238). The relevant regression checks in [javascriptStaticApiIdentity.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptStaticApiIdentity.test.ts), [javascriptStaticPathResolution.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptStaticPathResolution.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#1569
No description provided.