[GH-ISSUE #3380] [Bug] Electron fuse inspection rejects absolute symlinks inside the framework #1246

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

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

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

CLI

Observed behavior

inspect-electron-fuses rejects a macOS application bundle when its Versions/Current symlink uses an absolute target inside the same Electron Framework.framework. The same bytes are accepted through a relative in-framework link or by selecting the resolved framework binary directly. The error incorrectly reports that the internal link escapes the framework.

The relative-link bundle exits with code 0 and reports one sentinel. The absolute-link bundle exits with code 1 and returns code: "invalid_request"; its input issue says Framework symlink escapes Electron Framework.framework. The filesystem check confirms that the absolute link resolves to the bundle's own version directory.

Selecting that absolute-link bundle's resolved Versions/A/Electron Framework binary directly exits with code 0, reports one complete sentinel, and returns the same SHA-256 digest as the relative control. That direct selection is an executed workaround.

Reproduction

  1. Start from a clean source checkout, install its locked dependencies, and build the runtime. These commands prepare the built source runtime used by the reproduction.
git clone https://github.com/morluto/rea.git rea-repro
cd rea-repro
git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. From the repository root, create a directory named sample-fuses and save the following as sample-fuses/sample-repro.mjs. This is the complete input script; it creates every other input file inline.
mkdir sample-fuses
import { mkdirSync, writeFileSync, symlinkSync, realpathSync } from 'node:fs';
import { resolve, join } from 'node:path';
import { spawnSync } from 'node:child_process';
const checkout = resolve(process.argv[2]);
const root = resolve(process.argv[3]);
mkdirSync(root, { recursive: true });
mkdirSync(join(root, 'home'), { recursive: true });
const header = Buffer.alloc(32);
header.writeUInt32LE(0xfeedfacf, 0);
header.writeUInt32LE(0x0100000c, 4);
header.writeUInt32LE(2, 12);
const bytes = Buffer.concat([header, Buffer.from('dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX'), Buffer.from([1,6,49,49,49,49,49,49])]);
const records = [];
for (const absolute of [false, true]) {
  const label = absolute ? 'absolute' : 'relative';
  const app = join(root, label + '.app');
  const framework = join(app, 'Contents', 'Frameworks', 'Electron Framework.framework');
  const version = join(framework, 'Versions', 'A');
  mkdirSync(version, { recursive: true });
  writeFileSync(join(version, 'Electron Framework'), bytes);
  symlinkSync(absolute ? version : 'A', join(framework, 'Versions', 'Current'));
  symlinkSync('Versions/Current/Electron Framework', join(framework, 'Electron Framework'));
  const selected = app;
  const run = spawnSync(process.execPath, [join(checkout, 'scripts/rea.mjs'), 'inspect-electron-fuses', selected, '--json'], { cwd: checkout, env: { ...process.env, HOME: join(root, 'home') }, encoding: 'utf8' });
  writeFileSync(join(root, label + '.stdout.json'), run.stdout);
  writeFileSync(join(root, label + '.stderr.txt'), run.stderr);
  records.push({ label, selected, link_target: realpathSync(join(framework, 'Versions/Current')), expected_target: realpathSync(version), exit_status: run.status, output: JSON.parse(run.stdout) });
  if (absolute) {
    const direct = spawnSync(process.execPath, [join(checkout, 'scripts/rea.mjs'), 'inspect-electron-fuses', join(version, 'Electron Framework'), '--json'], { cwd: checkout, env: { ...process.env, HOME: join(root, 'home') }, encoding: 'utf8' });
    writeFileSync(join(root, 'direct.stdout.json'), direct.stdout);
    records.push({ label: 'absolute-direct-workaround', exit_status: direct.status, output: JSON.parse(direct.stdout) });
  }
}
writeFileSync(join(root, 'observations.json'), JSON.stringify(records, null, 2));
console.log(JSON.stringify(records.map(({label, exit_status, output, link_target, expected_target}) => ({ label, exit_status, link_target, expected_target, code: output.code, format: output.normalized_result?.artifact.format, sentinels: output.normalized_result?.slices[0]?.sentinel_count })), null, 2));
  1. Run it with a previously absent output directory. It creates an isolated home, invokes inspect-electron-fuses for both bundles, and checks the direct-binary workaround. It saves stdout and stderr for both bundle inspections, the direct-binary stdout, and observations.json in the output directory.
node sample-fuses/sample-repro.mjs "$PWD" sample-fuses/run

The relative control's Versions/Current points to A. The failing bundle's link points absolutely to its own Versions/A. For each bundle, the script verifies that the link's resolved path equals that bundle's internal version directory. The public CLI only reads the resulting files; it does not launch an application.

Expected behavior

Both bundle selections should resolve the in-framework link and report the same binary digest, complete wire, and six enabled fuses. An absolute spelling should not make a link that resolves inside the framework an escape.

docs/electron-fuses.md:35-39 specifies macOS bundle/framework resolution and follows symlinks while they remain inside the framework. docs/electron-fuses.md:81-87 repeats that boundary and promises read-only inspection.

Impact and scope

Impact level: P2, supported. Bundle fuse inspection fails for an observed in-framework absolute symlink even though the selected binary and its bytes are supported. The failure is bounded to this link form and is reversible without changing the application.

Workaround status: available. Selecting the resolved Versions/A/Electron Framework binary directly was executed and returned the same digest and fuse state as the successful bundle control.

The synthetic bytes isolate path resolution and do not establish behavior of a launched Electron runtime.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1; built source runtime. The host architecture was not recorded in the reproduction evidence; the fixture binaries are thin arm64 Mach-O.

Target and artifact context

Two synthetic macOS .app bundles contain identical thin arm64 Mach-O headers and complete fuse wires with six enabled fuses. Each bundle stores its binary at Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework and links the top-level Electron Framework to Versions/Current/Electron Framework. Only the spelling of Versions/Current differs: relative A or an absolute path to that framework's own Versions/A. No Electron installation or application launch is required by this fixture.

Interface

CLI

Earliest failure stage

Request parsing or validation

Relevant output or evidence

The executed public CLI reproduction established the following reduced observations:

Relative-link bundle: exit code 0; one sentinel.
Absolute-link bundle: exit code 1; code: "invalid_request".
Input issue: Framework symlink escapes Electron Framework.framework
Absolute direct-binary selection: exit code 0; one complete sentinel; same SHA-256 digest as the relative control.

For both bundles, realpathSync confirms that Versions/Current resolves to that bundle's own internal version directory. These CLI results are observed and locally reproduced; the reproduction is complete.

Typed outcome or recovery expected

Run the two bundle selections against fresh copies of the fixture. Both should succeed with matching binary digests, complete six-fuse wires, and one sentinel per slice. Keep the direct-binary result unchanged. Verify separately that links whose resolved targets actually leave the framework continue to be rejected; accepting the internal absolute link must preserve the documented containment rule.

Analysis or suspected cause

Source-derived reasoning: src/electron/ElectronFusePath.ts:208-214 classifies an absolute symlink target as an escape before checking where it resolves. This explains why the public CLI rejects only the absolute in-framework link despite the independent filesystem containment check. The executed public CLI fixture result is the primary violation evidence; source inspection explains the observed boundary separately.

Additional context

Related #3303 loses literal symlink target distinctions during process capture; this report concerns rejection of an existing internal framework link during fuse inspection.

Related #3324 changes the selected SQLite database through symlink and parent-component path normalization; this report rejects an absolute framework link that resolves to the intended binary.

Related #2272, closed as completed, misattributes unresolvable paths in capture_electron_scenario; this report concerns an existing resolvable link in read-only inspect-electron-fuses.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3380 ### 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 CLI ### Observed behavior `inspect-electron-fuses` rejects a macOS application bundle when its `Versions/Current` symlink uses an absolute target inside the same `Electron Framework.framework`. The same bytes are accepted through a relative in-framework link or by selecting the resolved framework binary directly. The error incorrectly reports that the internal link escapes the framework. The relative-link bundle exits with code `0` and reports one sentinel. The absolute-link bundle exits with code `1` and returns `code: "invalid_request"`; its input issue says `Framework symlink escapes Electron Framework.framework`. The filesystem check confirms that the absolute link resolves to the bundle's own version directory. Selecting that absolute-link bundle's resolved `Versions/A/Electron Framework` binary directly exits with code `0`, reports one complete sentinel, and returns the same SHA-256 digest as the relative control. That direct selection is an executed workaround. ### Reproduction 1. Start from a clean source checkout, install its locked dependencies, and build the runtime. These commands prepare the built source runtime used by the reproduction. ```bash git clone https://github.com/morluto/rea.git rea-repro cd rea-repro git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. From the repository root, create a directory named `sample-fuses` and save the following as `sample-fuses/sample-repro.mjs`. This is the complete input script; it creates every other input file inline. ```bash mkdir sample-fuses ``` ```javascript import { mkdirSync, writeFileSync, symlinkSync, realpathSync } from 'node:fs'; import { resolve, join } from 'node:path'; import { spawnSync } from 'node:child_process'; const checkout = resolve(process.argv[2]); const root = resolve(process.argv[3]); mkdirSync(root, { recursive: true }); mkdirSync(join(root, 'home'), { recursive: true }); const header = Buffer.alloc(32); header.writeUInt32LE(0xfeedfacf, 0); header.writeUInt32LE(0x0100000c, 4); header.writeUInt32LE(2, 12); const bytes = Buffer.concat([header, Buffer.from('dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX'), Buffer.from([1,6,49,49,49,49,49,49])]); const records = []; for (const absolute of [false, true]) { const label = absolute ? 'absolute' : 'relative'; const app = join(root, label + '.app'); const framework = join(app, 'Contents', 'Frameworks', 'Electron Framework.framework'); const version = join(framework, 'Versions', 'A'); mkdirSync(version, { recursive: true }); writeFileSync(join(version, 'Electron Framework'), bytes); symlinkSync(absolute ? version : 'A', join(framework, 'Versions', 'Current')); symlinkSync('Versions/Current/Electron Framework', join(framework, 'Electron Framework')); const selected = app; const run = spawnSync(process.execPath, [join(checkout, 'scripts/rea.mjs'), 'inspect-electron-fuses', selected, '--json'], { cwd: checkout, env: { ...process.env, HOME: join(root, 'home') }, encoding: 'utf8' }); writeFileSync(join(root, label + '.stdout.json'), run.stdout); writeFileSync(join(root, label + '.stderr.txt'), run.stderr); records.push({ label, selected, link_target: realpathSync(join(framework, 'Versions/Current')), expected_target: realpathSync(version), exit_status: run.status, output: JSON.parse(run.stdout) }); if (absolute) { const direct = spawnSync(process.execPath, [join(checkout, 'scripts/rea.mjs'), 'inspect-electron-fuses', join(version, 'Electron Framework'), '--json'], { cwd: checkout, env: { ...process.env, HOME: join(root, 'home') }, encoding: 'utf8' }); writeFileSync(join(root, 'direct.stdout.json'), direct.stdout); records.push({ label: 'absolute-direct-workaround', exit_status: direct.status, output: JSON.parse(direct.stdout) }); } } writeFileSync(join(root, 'observations.json'), JSON.stringify(records, null, 2)); console.log(JSON.stringify(records.map(({label, exit_status, output, link_target, expected_target}) => ({ label, exit_status, link_target, expected_target, code: output.code, format: output.normalized_result?.artifact.format, sentinels: output.normalized_result?.slices[0]?.sentinel_count })), null, 2)); ``` 3. Run it with a previously absent output directory. It creates an isolated home, invokes `inspect-electron-fuses` for both bundles, and checks the direct-binary workaround. It saves stdout and stderr for both bundle inspections, the direct-binary stdout, and `observations.json` in the output directory. ```bash node sample-fuses/sample-repro.mjs "$PWD" sample-fuses/run ``` The relative control's `Versions/Current` points to `A`. The failing bundle's link points absolutely to its own `Versions/A`. For each bundle, the script verifies that the link's resolved path equals that bundle's internal version directory. The public CLI only reads the resulting files; it does not launch an application. ### Expected behavior Both bundle selections should resolve the in-framework link and report the same binary digest, complete wire, and six enabled fuses. An absolute spelling should not make a link that resolves inside the framework an escape. `docs/electron-fuses.md:35-39` specifies macOS bundle/framework resolution and follows symlinks while they remain inside the framework. `docs/electron-fuses.md:81-87` repeats that boundary and promises read-only inspection. ### Impact and scope Impact level: P2, supported. Bundle fuse inspection fails for an observed in-framework absolute symlink even though the selected binary and its bytes are supported. The failure is bounded to this link form and is reversible without changing the application. Workaround status: available. Selecting the resolved `Versions/A/Electron Framework` binary directly was executed and returned the same digest and fuse state as the successful bundle control. The synthetic bytes isolate path resolution and do not establish behavior of a launched Electron runtime. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1; built source runtime. The host architecture was not recorded in the reproduction evidence; the fixture binaries are thin arm64 Mach-O. ### Target and artifact context Two synthetic macOS `.app` bundles contain identical thin arm64 Mach-O headers and complete fuse wires with six enabled fuses. Each bundle stores its binary at `Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework` and links the top-level `Electron Framework` to `Versions/Current/Electron Framework`. Only the spelling of `Versions/Current` differs: relative `A` or an absolute path to that framework's own `Versions/A`. No Electron installation or application launch is required by this fixture. ### Interface CLI ### Earliest failure stage Request parsing or validation ### Relevant output or evidence The executed public CLI reproduction established the following reduced observations: ```text Relative-link bundle: exit code 0; one sentinel. Absolute-link bundle: exit code 1; code: "invalid_request". Input issue: Framework symlink escapes Electron Framework.framework Absolute direct-binary selection: exit code 0; one complete sentinel; same SHA-256 digest as the relative control. ``` For both bundles, `realpathSync` confirms that `Versions/Current` resolves to that bundle's own internal version directory. These CLI results are observed and locally reproduced; the reproduction is complete. ### Typed outcome or recovery expected Run the two bundle selections against fresh copies of the fixture. Both should succeed with matching binary digests, complete six-fuse wires, and one sentinel per slice. Keep the direct-binary result unchanged. Verify separately that links whose resolved targets actually leave the framework continue to be rejected; accepting the internal absolute link must preserve the documented containment rule. ### Analysis or suspected cause Source-derived reasoning: `src/electron/ElectronFusePath.ts:208-214` classifies an absolute symlink target as an escape before checking where it resolves. This explains why the public CLI rejects only the absolute in-framework link despite the independent filesystem containment check. The executed public CLI fixture result is the primary violation evidence; source inspection explains the observed boundary separately. ### Additional context Related [#3303](https://github.com/morluto/rea/issues/3303) loses literal symlink target distinctions during process capture; this report concerns rejection of an existing internal framework link during fuse inspection. Related [#3324](https://github.com/morluto/rea/issues/3324) changes the selected SQLite database through symlink and parent-component path normalization; this report rejects an absolute framework link that resolves to the intended binary. Related [#2272](https://github.com/morluto/rea/issues/2272), closed as completed, misattributes unresolvable paths in `capture_electron_scenario`; this report concerns an existing resolvable link in read-only `inspect-electron-fuses`.
Author
Owner

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

Implemented in https://github.com/morluto/rea/pull/3587.

<!-- gh-comment-id:6174280530 --> @mvanhorn commented on GitHub (Oct 14, 2026): Implemented in https://github.com/morluto/rea/pull/3587.
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#1246
No description provided.