[GH-ISSUE #3213] [Bug] Missing ASAR companion bytes disappear from application analysis under complete coverage #1152

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

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

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

Analyzing a direct ASAR with analyze-javascript-application returns complete application-graph coverage when an inventoried unpacked native add-on's companion bytes are unavailable. Embedded JavaScript still requires ./addon.node, but the application result drops that known entry and reports the import as not-found. No limitation in the returned Evidence identifies the missing companion bytes.

An independent reproduction ran the same archive with its companion present, missing, and restored. All three CLI calls exited 0 and returned ordinary Evidence at normalized_result. The missing case had relevant_files: 2, no native-addon node, no ASAR-entry node for addon.node, and no unknown-scope node. All three import records containing both resolution fields reported resolved_path: null and resolution_status: "not-found". The present and restored controls each had relevant_files: 3, one native-addon node, one ASAR-entry node for addon.node, and resolved imports.

Reproduction

Use a clean checkout of the revision recorded below. Installation and build are prerequisites; the independent verification used prepared locked dependencies and the built runtime. It executed fixture construction and all three analysis calls.

  1. Install the locked dependencies and build the CLI.
npm ci
npm run build:cached
  1. Create a minimal ASAR with one unpacked native entry. The eight-byte add-on is inert fixture data; the static analysis does not load it as native code.
node --input-type=module <<'JS'
import { mkdir, writeFile } from "node:fs/promises";
import { createPackageWithOptions } from "@electron/asar";

await mkdir("repro-companion");
await mkdir("repro-companion/app-src");
await writeFile("repro-companion/app-src/package.json",
  '{"name":"companion-repro","version":"1.0.0","main":"main.js"}\n');
await writeFile("repro-companion/app-src/main.js",
  'const addon = require("./addon.node");\nmodule.exports = addon;\n');
await writeFile("repro-companion/app-src/addon.node",
  Buffer.from([0x7f, 0x45, 0x4c, 0x46, 2, 1, 1, 0]));
await createPackageWithOptions(
  "repro-companion/app-src", "repro-companion/app.asar", { unpack: "*.node" });
JS
  1. Analyze the same archive with the companion present, missing, and restored.
node scripts/rea.mjs analyze-javascript-application repro-companion/app.asar --json > repro-companion/present.json
mv repro-companion/app.asar.unpacked repro-companion/saved-companion
node scripts/rea.mjs analyze-javascript-application repro-companion/app.asar --json > repro-companion/missing.json
mv repro-companion/saved-companion repro-companion/app.asar.unpacked
node scripts/rea.mjs analyze-javascript-application repro-companion/app.asar --json > repro-companion/restored.json
  1. Inspect the result fields and compare the controls.
python3 - <<'PY'
import json
from pathlib import Path

for case in ("present", "missing", "restored"):
    result = json.loads(Path(f"repro-companion/{case}.json").read_text())["normalized_result"]
    graph = result["graph"]
    imports = []
    def visit(value):
        if isinstance(value, dict):
            if value.get("specifier") == "./addon.node" and "resolution_status" in value and "resolved_path" in value:
                imports.append({key: value[key] for key in ("resolved_path", "resolution_status")})
            for child in value.values():
                visit(child)
        elif isinstance(value, list):
            for child in value:
                visit(child)
    visit(graph)
    print(case, {
        "coverage": graph["coverage"],
        "relevant_files": result["statistics"]["relevant_files"],
        "native_addons": sum(node["kind"] == "native-addon" for node in graph["nodes"]),
        "unknown_scopes": sum(node["kind"] == "unknown-scope" for node in graph["nodes"]),
        "imports": imports,
        "limitations": graph["limitations"],
    })
PY

The missing case reports complete coverage and not-found imports while dropping the native entry. The present and restored cases retain the entry and resolved imports.

Expected behavior

Missing companion bytes must remain explicitly unavailable while REA continues analyzing embedded JavaScript. docs/javascript-artifact-reconstruction.md:158-162 requires the missing native/resource bytes to remain unknown, with hash_status: unavailable and no content-addressed child artifact. The application result must preserve this known unavailable entry and distinguish it from a path that was never inventoried.

docs/javascript-artifact-reconstruction.md:436-439 requires coverage to report omitted input. docs/javascript-application-graph.md:146-151 says complete coverage cannot omit items and unavailable values must remain explicit.

Impact and scope

This is a bounded evidence-integrity defect for direct ASAR input with missing unpacked companion bytes. Consumers can mistake a known unavailable native entry for an absent target, and the application result does not identify the bytes they need to supply. Restoring the original companion is a demonstrated workaround when callers possess those bytes. No verified workaround produces an honest unavailable-entry application result when those bytes cannot be supplied.

Only the CLI was exercised. Source inspection shows that MCP registration invokes the same validated application service, but no MCP client call was executed. No security-boundary violation or target application execution was observed.

REA version

6.4.0 (main at 5830088520)

Runtime and platform

Node v26.10.0; Darwin 27.0.0 arm64. The repository's .nvmrc declares 24.18.0, so this observed runtime differs from the development pin. No second runtime was tested.

Target and artifact context

A direct app.asar constructed with the checkout's locked @electron/asar dependency and unpack: "*.node". It contains package.json, embedded main.js, and a declared eight-byte addon.node with companion bytes outside the archive. Analysis used default options and did not execute the application.

Interface

CLI

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

Selected fields from the observed missing-companion application result:

graph.coverage: {"status":"complete","truncated":false,"omitted_count":0,"limits":[]}
statistics.relevant_files: 2
native-addon nodes: 0
ASAR-entry nodes for addon.node: 0
unknown-scope nodes: 0
./addon.node: resolved_path=null, resolution_status="not-found" (3 records)
No returned limitation identifies addon.node, unpacked content, or unavailable bytes.

Present and restored controls:
statistics.relevant_files: 3
native-addon nodes: 1
ASAR-entry nodes for addon.node: 1
./addon.node imports resolve to addon.node.

A separate inventory scan of the missing-companion archive retained addon.node with artifact_id: null, declared_size: 8, and hash_status: "unavailable". Its limitation was ASAR unpacked companion bytes were unavailable; no content hash or child artifact was produced. The application result loses this information. The inventory scan corroborates the public CLI reproduction.

Typed outcome or recovery expected

Continue embedded JavaScript analysis, identify the known unavailable addon.node bytes, and reflect the omitted input in coverage. The caller must be able to distinguish an unavailable declared entry from a genuinely absent path. The present and restored controls must keep their native-addon node and resolved import. This report does not prescribe a graph schema or request a digest for unavailable bytes.

Analysis or suspected cause

Source-derived reasoning at the recorded baseline:

  • src/artifacts/javascript/JavaScriptArtifactFiles.ts:394-412 collects relevant null-artifact occurrences into unavailableFiles. Its traversal at :256-257 skips entries absent from the expected-file map. The guard at :98-111 rejects explicitly selected unavailable sources, but this reproduction uses default full analysis.
  • src/application/javascript/JavaScriptArtifactGraphStructure.ts:136-151 projects analyzed files, so this skipped entry does not produce its native-addon or ASAR-entry nodes.
  • src/application/javascript/JavaScriptArtifactGraphBuilder.ts:166-232 selects coverage without accounting for these missing companion bytes.
  • src/server/registerElectronTools.ts:100-120 invokes the same validated application service for MCP. This is inspected reachability, not an observed MCP reproduction.

Additional context

Related reports are #2603, #2641, and #2834, and related merged fixes are #280 and #1400; this report concerns an unavailable relevant ASAR entry lost after inventory, whereas those cover Android projection, readable opaque modules, inline HTML, nonfatal inventory handling, and extraction/live Electron behavior.

P2 is supported because the application result silently loses an inventoried native entry and misstates coverage for this bounded input class. Present/restored controls recover the entry and imports when the original companion bytes are available.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3213 ### 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 Analyzing a direct ASAR with `analyze-javascript-application` returns complete application-graph coverage when an inventoried unpacked native add-on's companion bytes are unavailable. Embedded JavaScript still requires `./addon.node`, but the application result drops that known entry and reports the import as `not-found`. No limitation in the returned Evidence identifies the missing companion bytes. An independent reproduction ran the same archive with its companion present, missing, and restored. All three CLI calls exited 0 and returned ordinary Evidence at `normalized_result`. The missing case had `relevant_files: 2`, no native-addon node, no ASAR-entry node for `addon.node`, and no unknown-scope node. All three import records containing both resolution fields reported `resolved_path: null` and `resolution_status: "not-found"`. The present and restored controls each had `relevant_files: 3`, one native-addon node, one ASAR-entry node for `addon.node`, and resolved imports. ### Reproduction Use a clean checkout of the revision recorded below. Installation and build are prerequisites; the independent verification used prepared locked dependencies and the built runtime. It executed fixture construction and all three analysis calls. 1. Install the locked dependencies and build the CLI. ```sh npm ci npm run build:cached ``` 2. Create a minimal ASAR with one unpacked native entry. The eight-byte add-on is inert fixture data; the static analysis does not load it as native code. ```sh node --input-type=module <<'JS' import { mkdir, writeFile } from "node:fs/promises"; import { createPackageWithOptions } from "@electron/asar"; await mkdir("repro-companion"); await mkdir("repro-companion/app-src"); await writeFile("repro-companion/app-src/package.json", '{"name":"companion-repro","version":"1.0.0","main":"main.js"}\n'); await writeFile("repro-companion/app-src/main.js", 'const addon = require("./addon.node");\nmodule.exports = addon;\n'); await writeFile("repro-companion/app-src/addon.node", Buffer.from([0x7f, 0x45, 0x4c, 0x46, 2, 1, 1, 0])); await createPackageWithOptions( "repro-companion/app-src", "repro-companion/app.asar", { unpack: "*.node" }); JS ``` 3. Analyze the same archive with the companion present, missing, and restored. ```sh node scripts/rea.mjs analyze-javascript-application repro-companion/app.asar --json > repro-companion/present.json mv repro-companion/app.asar.unpacked repro-companion/saved-companion node scripts/rea.mjs analyze-javascript-application repro-companion/app.asar --json > repro-companion/missing.json mv repro-companion/saved-companion repro-companion/app.asar.unpacked node scripts/rea.mjs analyze-javascript-application repro-companion/app.asar --json > repro-companion/restored.json ``` 4. Inspect the result fields and compare the controls. ```sh python3 - <<'PY' import json from pathlib import Path for case in ("present", "missing", "restored"): result = json.loads(Path(f"repro-companion/{case}.json").read_text())["normalized_result"] graph = result["graph"] imports = [] def visit(value): if isinstance(value, dict): if value.get("specifier") == "./addon.node" and "resolution_status" in value and "resolved_path" in value: imports.append({key: value[key] for key in ("resolved_path", "resolution_status")}) for child in value.values(): visit(child) elif isinstance(value, list): for child in value: visit(child) visit(graph) print(case, { "coverage": graph["coverage"], "relevant_files": result["statistics"]["relevant_files"], "native_addons": sum(node["kind"] == "native-addon" for node in graph["nodes"]), "unknown_scopes": sum(node["kind"] == "unknown-scope" for node in graph["nodes"]), "imports": imports, "limitations": graph["limitations"], }) PY ``` The missing case reports complete coverage and not-found imports while dropping the native entry. The present and restored cases retain the entry and resolved imports. ### Expected behavior Missing companion bytes must remain explicitly unavailable while REA continues analyzing embedded JavaScript. `docs/javascript-artifact-reconstruction.md:158-162` requires the missing native/resource bytes to remain unknown, with `hash_status: unavailable` and no content-addressed child artifact. The application result must preserve this known unavailable entry and distinguish it from a path that was never inventoried. `docs/javascript-artifact-reconstruction.md:436-439` requires coverage to report omitted input. `docs/javascript-application-graph.md:146-151` says complete coverage cannot omit items and unavailable values must remain explicit. ### Impact and scope This is a bounded evidence-integrity defect for direct ASAR input with missing unpacked companion bytes. Consumers can mistake a known unavailable native entry for an absent target, and the application result does not identify the bytes they need to supply. Restoring the original companion is a demonstrated workaround when callers possess those bytes. No verified workaround produces an honest unavailable-entry application result when those bytes cannot be supplied. Only the CLI was exercised. Source inspection shows that MCP registration invokes the same validated application service, but no MCP client call was executed. No security-boundary violation or target application execution was observed. ### REA version 6.4.0 (main at 58300885207133422af235e0f2b10658cf958b03) ### Runtime and platform Node v26.10.0; Darwin 27.0.0 arm64. The repository's `.nvmrc` declares 24.18.0, so this observed runtime differs from the development pin. No second runtime was tested. ### Target and artifact context A direct `app.asar` constructed with the checkout's locked `@electron/asar` dependency and `unpack: "*.node"`. It contains `package.json`, embedded `main.js`, and a declared eight-byte `addon.node` with companion bytes outside the archive. Analysis used default options and did not execute the application. ### Interface CLI ### Earliest failure stage Artifact or evidence registration ### Relevant output or evidence Selected fields from the observed missing-companion application result: ```text graph.coverage: {"status":"complete","truncated":false,"omitted_count":0,"limits":[]} statistics.relevant_files: 2 native-addon nodes: 0 ASAR-entry nodes for addon.node: 0 unknown-scope nodes: 0 ./addon.node: resolved_path=null, resolution_status="not-found" (3 records) No returned limitation identifies addon.node, unpacked content, or unavailable bytes. Present and restored controls: statistics.relevant_files: 3 native-addon nodes: 1 ASAR-entry nodes for addon.node: 1 ./addon.node imports resolve to addon.node. ``` A separate inventory scan of the missing-companion archive retained `addon.node` with `artifact_id: null`, `declared_size: 8`, and `hash_status: "unavailable"`. Its limitation was `ASAR unpacked companion bytes were unavailable; no content hash or child artifact was produced.` The application result loses this information. The inventory scan corroborates the public CLI reproduction. ### Typed outcome or recovery expected Continue embedded JavaScript analysis, identify the known unavailable `addon.node` bytes, and reflect the omitted input in coverage. The caller must be able to distinguish an unavailable declared entry from a genuinely absent path. The present and restored controls must keep their native-addon node and resolved import. This report does not prescribe a graph schema or request a digest for unavailable bytes. ### Analysis or suspected cause Source-derived reasoning at the recorded baseline: - `src/artifacts/javascript/JavaScriptArtifactFiles.ts:394-412` collects relevant null-artifact occurrences into `unavailableFiles`. Its traversal at `:256-257` skips entries absent from the expected-file map. The guard at `:98-111` rejects explicitly selected unavailable sources, but this reproduction uses default full analysis. - `src/application/javascript/JavaScriptArtifactGraphStructure.ts:136-151` projects analyzed files, so this skipped entry does not produce its native-addon or ASAR-entry nodes. - `src/application/javascript/JavaScriptArtifactGraphBuilder.ts:166-232` selects coverage without accounting for these missing companion bytes. - `src/server/registerElectronTools.ts:100-120` invokes the same validated application service for MCP. This is inspected reachability, not an observed MCP reproduction. ### Additional context Related reports are [#2603](https://github.com/morluto/rea/issues/2603), [#2641](https://github.com/morluto/rea/issues/2641), and [#2834](https://github.com/morluto/rea/issues/2834), and related merged fixes are [#280](https://github.com/morluto/rea/pull/280) and [#1400](https://github.com/morluto/rea/pull/1400); this report concerns an unavailable relevant ASAR entry lost after inventory, whereas those cover Android projection, readable opaque modules, inline HTML, nonfatal inventory handling, and extraction/live Electron behavior. P2 is supported because the application result silently loses an inventoried native entry and misstates coverage for this bounded input class. Present/restored controls recover the entry and imports when the original companion bytes are available.
bsakaguchi 2026-10-15 18:31:32 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Reopening after review of the published fix: a follow-up review found that an inventoried unavailable same-container pkg/package.json is still treated as absent by readPackageMetadata in src/application/javascript/JavaScriptArtifactPathResolution.ts. With a readable pkg/index.js, the absent-metadata path can select the index even though the manifest bytes are unavailable, so package entry selection is still unknown. The unavailable-file lookup is used for path candidates and directory discovery, but this metadata reader does not consult it. The broader missing-ASAR-companion behavior is fixed; this narrower package-manifest case needs the resolver follow-up before closure. No real-provider claim is involved.

<!-- gh-comment-id:6164717980 --> @morluto commented on GitHub (Oct 14, 2026): Reopening after review of the published fix: a follow-up review found that an inventoried unavailable same-container `pkg/package.json` is still treated as absent by `readPackageMetadata` in `src/application/javascript/JavaScriptArtifactPathResolution.ts`. With a readable `pkg/index.js`, the absent-metadata path can select the index even though the manifest bytes are unavailable, so package entry selection is still unknown. The unavailable-file lookup is used for path candidates and directory discovery, but this metadata reader does not consult it. The broader missing-ASAR-companion behavior is fixed; this narrower package-manifest case needs the resolver follow-up before closure. No real-provider claim is involved.
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

The earlier reopened follow-up is resolved by published commit 322506d0c0: readPackageMetadata now checks same-container inventoried unavailable package.json facts before reporting metadata absent, so unknown package configuration cannot select index.js or guess exports. Focused owner tests cover unavailable/restored manifests and same-container behavior; tests/boundary/cli/javascriptSelfImportRelationships.test.ts exercises an actual ASAR with missing and restored unpacked manifests across CommonJS require and ESM import, checking unavailable/partial coverage before restoration and correct package targets afterward. Parent reports 10 focused files/293 cases plus build/docs/static guards. No provider execution was involved. Latest commit CI is pending.

<!-- gh-comment-id:6165001573 --> @morluto commented on GitHub (Oct 14, 2026): The earlier reopened follow-up is resolved by published commit 322506d0c0d39d3cd1e2e1c285c2cc23cfa73ee7: `readPackageMetadata` now checks same-container inventoried unavailable package.json facts before reporting metadata absent, so unknown package configuration cannot select index.js or guess exports. Focused owner tests cover unavailable/restored manifests and same-container behavior; `tests/boundary/cli/javascriptSelfImportRelationships.test.ts` exercises an actual ASAR with missing and restored unpacked manifests across CommonJS require and ESM import, checking unavailable/partial coverage before restoration and correct package targets afterward. Parent reports 10 focused files/293 cases plus build/docs/static guards. No provider execution was involved. Latest commit CI is pending.
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#1152
No description provided.