[GH-ISSUE #7623] [Bug] CommonJS uppercase extensions acquire JSON and native-addon loader relationships #4551

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

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

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

node scripts/rea.mjs analyze-javascript-application <application-directory> --json emits a native-addon loads relationship for a CommonJS require("./addon.NODE"), although Node 24.22.1 executes that exact file as JavaScript. A sibling require("./data.JSON") gets target_file_kind: "json" and JSON parse status even when Node successfully executes its JavaScript exports.

With JavaScript exports in both uppercase files, Node returns both values; REA reports JSON status invalid for data.JSON and a native load for addon.NODE. Changing only data.JSON to JSON object text makes Node throw a JavaScript syntax error, while REA reports JSON status included. Each REA command exits 0 and returns complete Evidence. These are incorrect static loader relationships, not observations that REA executed the target.

Reproduction

  1. Use the built baseline checkout and its pinned Node 24.22.1, with node on PATH.
  2. Run the following script from that checkout. It creates all fixtures, isolates HOME/XDG, invokes the actual CLI and Node oracle, then removes its temporary files. The two target filenames have no separate lowercase twin directory entries.
python3 - <<'PY'
import json, os, subprocess, tempfile
from pathlib import Path

repo = Path.cwd()
with tempfile.TemporaryDirectory(prefix="rea-extension-case-") as temporary:
    root = Path(temporary)
    app = root / "app"
    app.mkdir()
    env = os.environ.copy()
    for key in ("HOME", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME"):
        directory = root / key.lower()
        directory.mkdir()
        env[key] = str(directory)
    (app / "package.json").write_text('{"name":"extension-case","version":"1.0.0","main":"index.cjs"}\n')
    (app / "index.cjs").write_text('try { console.log("JSON:", require("./data.JSON")); } catch (error) { console.log(error.name + ": " + error.message); }\ntry { console.log("NODE:", require("./addon.NODE")); } catch (error) { console.log(error.name + ": " + error.message); }\n')
    (app / "addon.NODE").write_text('module.exports = { loadedBy: "javascript" };\n')
    for case, contents in [("javascript", 'module.exports = { loadedBy: "javascript" };\n'), ("json-text", '{"ok":true}\n')]:
        (app / "data.JSON").write_text(contents)
        print(case, "entries:", sorted(p.name for p in app.iterdir()))
        oracle = subprocess.run(["node", "index.cjs"], cwd=app, env=env, text=True, capture_output=True, check=True)
        print(oracle.stdout.strip())
        result = subprocess.run(["node", "scripts/rea.mjs", "analyze-javascript-application", str(app), "--json"], cwd=repo, env=env, text=True, capture_output=True, check=True)
        graph = json.loads(result.stdout)["normalized_result"]["graph"]
        for edge in graph["edges"]:
            properties = edge.get("properties", {})
            if properties.get("specifier") in ("./data.JSON", "./addon.NODE"):
                print(json.dumps({"relation": edge["relation"], **{k: properties[k] for k in ("specifier", "resolved_path", "resolution_status", "target_file_kind", "target_json_status") if k in properties}}, sort_keys=True))
PY
  1. Compare the successful JavaScript exports from Node with REA's native loads edge and JSON target status. The JSON-text variant discriminates JSON parsing from JavaScript loading.

Both payload cases reproduced in the initial direct literal-require runs and in two complete executions of this script. All six REA analysis invocations for those cases returned exit 0. A preliminary computed-require probe was discarded because it did not establish a literal module edge.

Expected behavior

Module relationships should respect the case of the filename used for loader selection. For the unmodified Node CommonJS loader tested here, uppercase .JSON and .NODE select JavaScript handling. The graph should retain the exact paths without inferring that these requests load JSON or a native addon.

General inventory classification may still identify JSON-looking or addon-looking files. That inventory policy must remain separate from runtime-loader interpretation. The baseline already contains an exact-lowercase JSON loader predicate, and its uppercase JSON ownership tests preserve this distinction; source references are below.

Impact and scope

P2: this bounded input produces incorrect machine-readable loader evidence in a successful application analysis. In particular, the native loads edge points to a text file that Node executes as JavaScript. The JSON edge attaches JSON-loader status to a JavaScript target. Exact paths and artifact bytes remain available, so callers can manually check the resolved suffix and Node's require behavior before relying on these relationships.

The demonstrated scope is literal relative CommonJS requires in a local four-file application on macOS. No downstream native-binding, ownership, feature-trace, real-application, ESM, MCP or ASAR consequence was verified. No automated graph-repair workaround was tested.

REA version

Exact stdout from node scripts/rea.mjs --version:

6.8.0

Source baseline: 65f1c447344a423fa2785847dbcea1e9a9e2454a.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1, darwin arm64. No analysis provider or GUI application was used.

Target and artifact context

Self-authored directory containing package.json, index.cjs, data.JSON and addon.NODE. The uppercase addon contains only JavaScript exports and is not a native binary. Only Node executes these inert fixtures; REA performs static analysis.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

For the JavaScript payload case, the script prints:

JSON: { loadedBy: 'javascript' }
NODE: { loadedBy: 'javascript' }

Selected REA rows from that same case:

{"relation":"imports","specifier":"./data.JSON","resolved_path":"data.JSON","resolution_status":"resolved","target_file_kind":"json","target_json_status":"invalid"}
{"relation":"imports","specifier":"./addon.NODE","resolved_path":"addon.NODE","resolution_status":"resolved","target_file_kind":"native-addon","target_json_status":null}
{"relation":"loads","specifier":"./addon.NODE","resolved_path":"addon.NODE","resolution_status":"resolved"}

With JSON text in data.JSON, Node prints SyntaxError: Unexpected token ':', and the semantic edge instead reports target_json_status: "included". The addon still executes as JavaScript and retains the native loads edge in REA.

A separate Node observation wrapped the existing .js handler without changing its implementation. It recorded JavaScript-loader calls for index.cjs, data.JSON and addon.NODE.

The host filesystem is case-insensitive: lowercase and uppercase lookup paths reached the same inode, while the directory contained only the uppercase entries. In that control, uppercase requests still selected JavaScript; lowercase requests selected the JSON/native loaders for the same bytes. Thus case-insensitive file lookup does not invalidate this exact-uppercase reproduction. No claim is made about custom loader hooks or other path/cache arrangements.

Supporting baseline sources:

  • src/domain/javascript/javascriptModuleResolutionMode.ts:4-10: the JSON target predicate requires the exact lowercase loader suffix and distinguishes it from inventory kind.
  • docs/javascript-artifact-reconstruction.md:963-980: CommonJS keeps literal filesystem spelling rather than ESM URL processing. This paragraph does not independently specify extension-case dispatch.
  • docs/javascript-application-graph.md:117-121,129-131: relationships are static inferences, with Node import/require modes kept distinct.
  • src/artifacts/javascript/JavaScriptArtifactFiles.ts:628-641: inventory suffix classification lowercases the path.
  • src/application/javascript/JavaScriptModuleRelationships.ts:324-336,398-417: semantic relationship metadata takes JSON status and target kind directly from that inventory classification.
  • src/application/javascript/JavaScriptArtifactGraphFindings.ts:306-311: the native loads relation is selected from the folded file kind.
  • tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts:99-240: uppercase JSON cases are excluded from inert JSON ownership handling.
  • tests/boundary/filesystem/javascriptArtifactGraphObservationLookup.test.ts:212-256: uppercase JSON inventory classification is intentional for omission metadata.

The existing uppercase tests were inspected. The permitted test:local invocation refused the built test project before executing tests and instructed use of test:focused; no passing test-suite claim is made. The actual built public CLI supplied the reproduction evidence above. No cleanup failure occurred in these verifier runs.

Typed outcome or recovery expected

Retain these dependencies and their exact filenames while withholding JSON/native-loader semantics that contradict Node's handling of the same requests. Keep general artifact inventory classification separate from that decision.

Analysis or suspected cause

Source-derived explanation: inventory lowercases filename suffixes, and the module graph reuses that kind for JSON status and native loads selection. The existing exact JSON loader predicate is used elsewhere but does not narrow these projections. This explains the observed discrepancy without requiring a global change to inventory case policy.

Additional context

Open/closed issue searches, open/closed/merged PR searches and comment searches found no matching uppercase loader-dispatch report. Queries included the exact filenames, isJsonModuleTarget, uppercase JavaScript, extension case, JSON dispatch, and native-addon case.

Related #5929 and merged PR #6402 concern an association when extension probing resolves addon.node.js; the baseline already checks inventory kind for that association. This report selects an exact uppercase filename whose inventory kind is folded, and demonstrates a native load plus JSON target status. It does not claim that fix regressed.

#5881 and merged PR #5905 preserve unknown JSON omission counts, including uppercase inventory files. PR #4327 concerns literal paths and ownership uncertainty, retaining the lowercase-versus-uppercase JSON distinction. Neither reports this graph projection. Also checked: #195, #1486, #6652, #6758, and #6860; they concern other formats or semantic owners.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Successful public CLI analysis emits a native load and JSON-loader metadata for exact uppercase requests that the independent Node oracle handles as JavaScript.
  • Workaround status: available
  • Workaround basis: Returned filenames and artifact bytes preserve the evidence needed to manually reject these loader interpretations using the exact resolved suffix and Node behavior, without altering application source.
Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7623 ### 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 `node scripts/rea.mjs analyze-javascript-application <application-directory> --json` emits a native-addon `loads` relationship for a CommonJS `require("./addon.NODE")`, although Node 24.22.1 executes that exact file as JavaScript. A sibling `require("./data.JSON")` gets `target_file_kind: "json"` and JSON parse status even when Node successfully executes its JavaScript exports. With JavaScript exports in both uppercase files, Node returns both values; REA reports JSON status `invalid` for `data.JSON` and a native load for `addon.NODE`. Changing only `data.JSON` to JSON object text makes Node throw a JavaScript syntax error, while REA reports JSON status `included`. Each REA command exits 0 and returns complete Evidence. These are incorrect static loader relationships, not observations that REA executed the target. ### Reproduction 1. Use the built baseline checkout and its pinned Node 24.22.1, with `node` on PATH. 2. Run the following script from that checkout. It creates all fixtures, isolates HOME/XDG, invokes the actual CLI and Node oracle, then removes its temporary files. The two target filenames have no separate lowercase twin directory entries. ```sh python3 - <<'PY' import json, os, subprocess, tempfile from pathlib import Path repo = Path.cwd() with tempfile.TemporaryDirectory(prefix="rea-extension-case-") as temporary: root = Path(temporary) app = root / "app" app.mkdir() env = os.environ.copy() for key in ("HOME", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME"): directory = root / key.lower() directory.mkdir() env[key] = str(directory) (app / "package.json").write_text('{"name":"extension-case","version":"1.0.0","main":"index.cjs"}\n') (app / "index.cjs").write_text('try { console.log("JSON:", require("./data.JSON")); } catch (error) { console.log(error.name + ": " + error.message); }\ntry { console.log("NODE:", require("./addon.NODE")); } catch (error) { console.log(error.name + ": " + error.message); }\n') (app / "addon.NODE").write_text('module.exports = { loadedBy: "javascript" };\n') for case, contents in [("javascript", 'module.exports = { loadedBy: "javascript" };\n'), ("json-text", '{"ok":true}\n')]: (app / "data.JSON").write_text(contents) print(case, "entries:", sorted(p.name for p in app.iterdir())) oracle = subprocess.run(["node", "index.cjs"], cwd=app, env=env, text=True, capture_output=True, check=True) print(oracle.stdout.strip()) result = subprocess.run(["node", "scripts/rea.mjs", "analyze-javascript-application", str(app), "--json"], cwd=repo, env=env, text=True, capture_output=True, check=True) graph = json.loads(result.stdout)["normalized_result"]["graph"] for edge in graph["edges"]: properties = edge.get("properties", {}) if properties.get("specifier") in ("./data.JSON", "./addon.NODE"): print(json.dumps({"relation": edge["relation"], **{k: properties[k] for k in ("specifier", "resolved_path", "resolution_status", "target_file_kind", "target_json_status") if k in properties}}, sort_keys=True)) PY ``` 3. Compare the successful JavaScript exports from Node with REA's native `loads` edge and JSON target status. The JSON-text variant discriminates JSON parsing from JavaScript loading. Both payload cases reproduced in the initial direct literal-require runs and in two complete executions of this script. All six REA analysis invocations for those cases returned exit 0. A preliminary computed-require probe was discarded because it did not establish a literal module edge. ### Expected behavior Module relationships should respect the case of the filename used for loader selection. For the unmodified Node CommonJS loader tested here, uppercase `.JSON` and `.NODE` select JavaScript handling. The graph should retain the exact paths without inferring that these requests load JSON or a native addon. General inventory classification may still identify JSON-looking or addon-looking files. That inventory policy must remain separate from runtime-loader interpretation. The baseline already contains an exact-lowercase JSON loader predicate, and its uppercase JSON ownership tests preserve this distinction; source references are below. ### Impact and scope P2: this bounded input produces incorrect machine-readable loader evidence in a successful application analysis. In particular, the native `loads` edge points to a text file that Node executes as JavaScript. The JSON edge attaches JSON-loader status to a JavaScript target. Exact paths and artifact bytes remain available, so callers can manually check the resolved suffix and Node's require behavior before relying on these relationships. The demonstrated scope is literal relative CommonJS requires in a local four-file application on macOS. No downstream native-binding, ownership, feature-trace, real-application, ESM, MCP or ASAR consequence was verified. No automated graph-repair workaround was tested. ### REA version Exact stdout from `node scripts/rea.mjs --version`: ```text 6.8.0 ``` Source baseline: `65f1c447344a423fa2785847dbcea1e9a9e2454a`. ### Runtime and platform Node.js v24.22.1; macOS 27.0.1, darwin arm64. No analysis provider or GUI application was used. ### Target and artifact context Self-authored directory containing `package.json`, `index.cjs`, `data.JSON` and `addon.NODE`. The uppercase addon contains only JavaScript exports and is not a native binary. Only Node executes these inert fixtures; REA performs static analysis. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence For the JavaScript payload case, the script prints: ```text JSON: { loadedBy: 'javascript' } NODE: { loadedBy: 'javascript' } ``` Selected REA rows from that same case: ```jsonl {"relation":"imports","specifier":"./data.JSON","resolved_path":"data.JSON","resolution_status":"resolved","target_file_kind":"json","target_json_status":"invalid"} {"relation":"imports","specifier":"./addon.NODE","resolved_path":"addon.NODE","resolution_status":"resolved","target_file_kind":"native-addon","target_json_status":null} {"relation":"loads","specifier":"./addon.NODE","resolved_path":"addon.NODE","resolution_status":"resolved"} ``` With JSON text in `data.JSON`, Node prints `SyntaxError: Unexpected token ':'`, and the semantic edge instead reports `target_json_status: "included"`. The addon still executes as JavaScript and retains the native `loads` edge in REA. A separate Node observation wrapped the existing `.js` handler without changing its implementation. It recorded JavaScript-loader calls for `index.cjs`, `data.JSON` and `addon.NODE`. The host filesystem is case-insensitive: lowercase and uppercase lookup paths reached the same inode, while the directory contained only the uppercase entries. In that control, uppercase requests still selected JavaScript; lowercase requests selected the JSON/native loaders for the same bytes. Thus case-insensitive file lookup does not invalidate this exact-uppercase reproduction. No claim is made about custom loader hooks or other path/cache arrangements. Supporting baseline sources: - `src/domain/javascript/javascriptModuleResolutionMode.ts:4-10`: the JSON target predicate requires the exact lowercase loader suffix and distinguishes it from inventory kind. - `docs/javascript-artifact-reconstruction.md:963-980`: CommonJS keeps literal filesystem spelling rather than ESM URL processing. This paragraph does not independently specify extension-case dispatch. - `docs/javascript-application-graph.md:117-121,129-131`: relationships are static inferences, with Node import/require modes kept distinct. - `src/artifacts/javascript/JavaScriptArtifactFiles.ts:628-641`: inventory suffix classification lowercases the path. - `src/application/javascript/JavaScriptModuleRelationships.ts:324-336,398-417`: semantic relationship metadata takes JSON status and target kind directly from that inventory classification. - `src/application/javascript/JavaScriptArtifactGraphFindings.ts:306-311`: the native `loads` relation is selected from the folded file kind. - `tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts:99-240`: uppercase JSON cases are excluded from inert JSON ownership handling. - `tests/boundary/filesystem/javascriptArtifactGraphObservationLookup.test.ts:212-256`: uppercase JSON inventory classification is intentional for omission metadata. The existing uppercase tests were inspected. The permitted `test:local` invocation refused the built test project before executing tests and instructed use of `test:focused`; no passing test-suite claim is made. The actual built public CLI supplied the reproduction evidence above. No cleanup failure occurred in these verifier runs. ### Typed outcome or recovery expected Retain these dependencies and their exact filenames while withholding JSON/native-loader semantics that contradict Node's handling of the same requests. Keep general artifact inventory classification separate from that decision. ### Analysis or suspected cause Source-derived explanation: inventory lowercases filename suffixes, and the module graph reuses that kind for JSON status and native `loads` selection. The existing exact JSON loader predicate is used elsewhere but does not narrow these projections. This explains the observed discrepancy without requiring a global change to inventory case policy. ### Additional context Open/closed issue searches, open/closed/merged PR searches and comment searches found no matching uppercase loader-dispatch report. Queries included the exact filenames, `isJsonModuleTarget`, uppercase JavaScript, extension case, JSON dispatch, and native-addon case. Related [#5929](https://github.com/morluto/rea/issues/5929) and merged [PR #6402](https://github.com/morluto/rea/pull/6402) concern an association when extension probing resolves `addon.node.js`; the baseline already checks inventory kind for that association. This report selects an exact uppercase filename whose inventory kind is folded, and demonstrates a native load plus JSON target status. It does not claim that fix regressed. [#5881](https://github.com/morluto/rea/issues/5881) and merged [PR #5905](https://github.com/morluto/rea/pull/5905) preserve unknown JSON omission counts, including uppercase inventory files. [PR #4327](https://github.com/morluto/rea/pull/4327) concerns literal paths and ownership uncertainty, retaining the lowercase-versus-uppercase JSON distinction. Neither reports this graph projection. Also checked: [#195](https://github.com/morluto/rea/issues/195), [#1486](https://github.com/morluto/rea/issues/1486), [#6652](https://github.com/morluto/rea/issues/6652), [#6758](https://github.com/morluto/rea/issues/6758), and [#6860](https://github.com/morluto/rea/issues/6860); they concern other formats or semantic owners. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Successful public CLI analysis emits a native load and JSON-loader metadata for exact uppercase requests that the independent Node oracle handles as JavaScript. - Workaround status: available - Workaround basis: Returned filenames and artifact bytes preserve the evidence needed to manually reject these loader interpretations using the exact resolved suffix and Node behavior, without altering application source.
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#4551
No description provided.