[GH-ISSUE #5929] [Bug] Native-addon association accepts a resolved JavaScript file #3604

Closed
opened 2026-10-31 00:42:19 +00:00 by bsakaguchi · 0 comments
Owner

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

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 public analyze command creates an associate-native-addon-requested-exports edge from a JavaScript file when require("./addon.node") resolves to addon.node.js and no addon.node exists. The source node is classified as javascript-asset, yet the edge's limitation says the .node artifact path is resolved. The same completed result reports native_addon_bindings: 1 and resolved_native_addon_bindings: 0.

The edge is explicitly inferred and has verified: false. The problem is its association with a non-native resolved artifact, not a claim of verified binary exports.

Reproduction

Run the following from a built source checkout with its installed dependencies. It invokes the real CLI sequentially for the affected input and two controls. It does not execute the input JavaScript or load the placeholder native file.

python3 - <<'PY'
import json
from pathlib import Path
import subprocess
from tempfile import TemporaryDirectory

with TemporaryDirectory(prefix="rea-addon-kind-") as temporary:
    root = Path(temporary)
    for name in ("javascript-fallback", "missing", "native-control"):
        app = root / name
        app.mkdir()
        (app / "main.js").write_text('const addon = require("./addon.node"); void addon;\n')
        if name != "missing":
            (app / "addon.node.js").write_text('module.exports = { requested: true };\n')
        if name == "native-control":
            (app / "addon.node").write_bytes(b"inert inventory-only placeholder")
        completed = subprocess.run(
            ["node", "scripts/rea.mjs", "analyze", str(app), "--json"],
            capture_output=True, text=True, timeout=60,
        )
        assert completed.returncode == 0, completed.stderr
        result = json.loads(completed.stdout)["normalized_result"]
        graph = result["graph"]
        nodes = {node["node_id"]: node for node in graph["nodes"]}
        associations = [edge for edge in graph["edges"]
                        if edge["evidence"]["extractor"]["operation"]
                        == "associate-native-addon-requested-exports"]
        print(json.dumps({
            "case": name,
            "native_addon_bindings": result["summary"]["native_addon_bindings"],
            "resolved_native_addon_bindings": result["summary"]["resolved_native_addon_bindings"],
            "associations": [{
                "source_kind": nodes[edge["source_node_id"]]["kind"],
                "resolved_path": edge["properties"]["resolved_path"],
                "target_kind": nodes[edge["target_node_id"]]["kind"],
            } for edge in associations],
        }))
PY

The JavaScript fallback reproduced once with Node v26.12.0 and again with Node v24.22.0. The two controls ran once with Node v24.22.0.

Expected behavior

Only a resolved file classified as native-addon should receive this native-addon-to-requested-binding association. When a module request resolves to a JavaScript file, preserve the requested binding and ordinary dependency information without asserting that a native artifact was resolved. The association and resolved-native-binding summary should agree on file kind.

The documented graph example describes a resolved native add-on exposing the requested binding. The summary implementation already requires kind: "native-addon".

Impact and scope

P2: a bounded input produces an incorrect machine-readable native relationship in completed, validated analysis Evidence. Consumers can reject the false association by checking its source node kind; the output retains that kind and the zero resolved-native count. The missing-target and exact-native-target controls behave consistently.

This was demonstrated for a local directory with a CommonJS request and extension probing to a JavaScript file. Frequency in real applications and downstream feature-trace effects were not measured. MCP and ASAR inputs were not run.

REA version

6.7.0 source checkout at 52415975942a4b9c53a817915b313ccc404f280e. The published package was not run.

Runtime and platform

macOS 27.0.1 arm64; Node.js v26.12.0 for the initial affected case and v24.22.0 for the three-case reproduction. No external analysis provider was required.

Target and artifact context

An inert directory containing main.js with require("./addon.node") and an ordinary JavaScript module named addon.node.js, without an exact addon.node. The native control additionally contains an inventory-only placeholder named addon.node; it is not a valid native binary and is never loaded.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The three-case script printed:

{"case": "javascript-fallback", "native_addon_bindings": 1, "resolved_native_addon_bindings": 0, "associations": [{"source_kind": "javascript-asset", "resolved_path": "addon.node.js", "target_kind": "native-export"}]}
{"case": "missing", "native_addon_bindings": 1, "resolved_native_addon_bindings": 0, "associations": []}
{"case": "native-control", "native_addon_bindings": 1, "resolved_native_addon_bindings": 1, "associations": [{"source_kind": "native-addon", "resolved_path": "addon.node", "target_kind": "native-export"}]}

For the affected case, the association has relation exposes, extractor operation associate-native-addon-requested-exports, state inferred, confidence medium, and verified: false. Its limitations include:

The .node artifact path is resolved, but requested JavaScript members have not been verified against native symbols.

The source node's inventory observation has path: "addon.node.js" and file_kind: "javascript". These values were read from the completed CLI Evidence.

Typed outcome or recovery expected

The JavaScript-fallback case should retain one requested binding and zero resolved native bindings, with no associate-native-addon-requested-exports edge from the JavaScript asset. The missing-target control should continue to have no association. The exact-native-target control should retain its association from a native-addon and its resolved-binding count of one.

Analysis or suspected cause

Source-derived: the native graph builder obtains any file node at the resolved path. Its association guard checks only that the node and path exist. The resolver's file candidate list permits the .js fallback. By contrast, the summary's resolution predicate checks the requested file kind.

Additional context

A source comparison against fetched main aa53c065387c58b4189d82c47f1f3445fc505e30 found no changes to the native graph builder, summary, or path resolver. That freshness check inspected source; it did not run a new build of main.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5929 ### 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 public `analyze` command creates an `associate-native-addon-requested-exports` edge from a JavaScript file when `require("./addon.node")` resolves to `addon.node.js` and no `addon.node` exists. The source node is classified as `javascript-asset`, yet the edge's limitation says the `.node` artifact path is resolved. The same completed result reports `native_addon_bindings: 1` and `resolved_native_addon_bindings: 0`. The edge is explicitly inferred and has `verified: false`. The problem is its association with a non-native resolved artifact, not a claim of verified binary exports. ### Reproduction Run the following from a built source checkout with its installed dependencies. It invokes the real CLI sequentially for the affected input and two controls. It does not execute the input JavaScript or load the placeholder native file. ```sh python3 - <<'PY' import json from pathlib import Path import subprocess from tempfile import TemporaryDirectory with TemporaryDirectory(prefix="rea-addon-kind-") as temporary: root = Path(temporary) for name in ("javascript-fallback", "missing", "native-control"): app = root / name app.mkdir() (app / "main.js").write_text('const addon = require("./addon.node"); void addon;\n') if name != "missing": (app / "addon.node.js").write_text('module.exports = { requested: true };\n') if name == "native-control": (app / "addon.node").write_bytes(b"inert inventory-only placeholder") completed = subprocess.run( ["node", "scripts/rea.mjs", "analyze", str(app), "--json"], capture_output=True, text=True, timeout=60, ) assert completed.returncode == 0, completed.stderr result = json.loads(completed.stdout)["normalized_result"] graph = result["graph"] nodes = {node["node_id"]: node for node in graph["nodes"]} associations = [edge for edge in graph["edges"] if edge["evidence"]["extractor"]["operation"] == "associate-native-addon-requested-exports"] print(json.dumps({ "case": name, "native_addon_bindings": result["summary"]["native_addon_bindings"], "resolved_native_addon_bindings": result["summary"]["resolved_native_addon_bindings"], "associations": [{ "source_kind": nodes[edge["source_node_id"]]["kind"], "resolved_path": edge["properties"]["resolved_path"], "target_kind": nodes[edge["target_node_id"]]["kind"], } for edge in associations], })) PY ``` The JavaScript fallback reproduced once with Node v26.12.0 and again with Node v24.22.0. The two controls ran once with Node v24.22.0. ### Expected behavior Only a resolved file classified as `native-addon` should receive this native-addon-to-requested-binding association. When a module request resolves to a JavaScript file, preserve the requested binding and ordinary dependency information without asserting that a native artifact was resolved. The association and resolved-native-binding summary should agree on file kind. The [documented graph example](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/docs/javascript-application-graph.md#L99-L117) describes a resolved native add-on exposing the requested binding. The [summary implementation](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/javascript/ElectronBoundaryAnalysis.ts#L172-L182) already requires `kind: "native-addon"`. ### Impact and scope P2: a bounded input produces an incorrect machine-readable native relationship in completed, validated analysis Evidence. Consumers can reject the false association by checking its source node kind; the output retains that kind and the zero resolved-native count. The missing-target and exact-native-target controls behave consistently. This was demonstrated for a local directory with a CommonJS request and extension probing to a JavaScript file. Frequency in real applications and downstream feature-trace effects were not measured. MCP and ASAR inputs were not run. ### REA version `6.7.0` source checkout at `52415975942a4b9c53a817915b313ccc404f280e`. The published package was not run. ### Runtime and platform macOS 27.0.1 arm64; Node.js v26.12.0 for the initial affected case and v24.22.0 for the three-case reproduction. No external analysis provider was required. ### Target and artifact context An inert directory containing `main.js` with `require("./addon.node")` and an ordinary JavaScript module named `addon.node.js`, without an exact `addon.node`. The native control additionally contains an inventory-only placeholder named `addon.node`; it is not a valid native binary and is never loaded. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The three-case script printed: ```jsonl {"case": "javascript-fallback", "native_addon_bindings": 1, "resolved_native_addon_bindings": 0, "associations": [{"source_kind": "javascript-asset", "resolved_path": "addon.node.js", "target_kind": "native-export"}]} {"case": "missing", "native_addon_bindings": 1, "resolved_native_addon_bindings": 0, "associations": []} {"case": "native-control", "native_addon_bindings": 1, "resolved_native_addon_bindings": 1, "associations": [{"source_kind": "native-addon", "resolved_path": "addon.node", "target_kind": "native-export"}]} ``` For the affected case, the association has relation `exposes`, extractor operation `associate-native-addon-requested-exports`, state `inferred`, confidence `medium`, and `verified: false`. Its limitations include: ```text The .node artifact path is resolved, but requested JavaScript members have not been verified against native symbols. ``` The source node's inventory observation has `path: "addon.node.js"` and `file_kind: "javascript"`. These values were read from the completed CLI Evidence. ### Typed outcome or recovery expected The JavaScript-fallback case should retain one requested binding and zero resolved native bindings, with no `associate-native-addon-requested-exports` edge from the JavaScript asset. The missing-target control should continue to have no association. The exact-native-target control should retain its association from a `native-addon` and its resolved-binding count of one. ### Analysis or suspected cause Source-derived: the [native graph builder](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/javascript/ElectronBoundaryGraphNative.ts#L48-L55) obtains any file node at the resolved path. Its [association guard](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/javascript/ElectronBoundaryGraphNative.ts#L117-L141) checks only that the node and path exist. The resolver's [file candidate list](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/javascript/JavaScriptArtifactPathResolution.ts#L1753-L1761) permits the `.js` fallback. By contrast, the [summary's resolution predicate](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/javascript/ElectronBoundaryAnalysis.ts#L218-L235) checks the requested file kind. ### Additional context A source comparison against fetched main `aa53c065387c58b4189d82c47f1f3445fc505e30` found no changes to the native graph builder, summary, or path resolver. That freshness check inspected source; it did not run a new build of 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#3604
No description provided.