[PR #2399] [MERGED] feat(electron): inspect packaged Electron fuse state without execution #5200

Closed
opened 2026-10-31 01:04:45 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/2399
Author: @mvanhorn
Created: 10/12/2026
Status: ✅ Merged
Merged: 10/14/2026
Merged by: @N0zoM1z0

Base: main ← Head: fix/electron-fuse-inspection


📝 Commits (10+)

  • fc916b5 feat(electron): inspect packaged Electron fuse state without execution
  • 92b8195 fix(electron): scope ASAR enforcement and framework symlink containment
  • 7902dcd fix(electron): contain direct framework binary selections
  • a8d4bf0 fix(electron): reject symlinked bundle directories above the framework
  • 36a38e1 refactor(electron): drop unused fuse type export
  • 0ef7206 chore: merge main and preserve Electron fuse inspection
  • f7b47f8 fix(electron): enforce fuse path and evidence boundaries
  • f39486d fix(electron): bind fuse reads to active framework identity
  • a13000f Merge commit '94072364c7' into HEAD
  • ef4c131 Merge remote-tracking branch 'origin/main' into fix/electron-fuse-inspection

📊 Changes

31 files changed (+3237 additions, -9 deletions)

View changed files

📝 .agents/skills/reverse-engineer-anything/SKILL.md (+1 -1)
📝 .agents/skills/reverse-engineer-anything/references/runtime-observation.md (+5 -3)
📝 docs/.vitepress/config.ts (+1 -0)
📝 docs/cli.md (+1 -0)
➕ docs/electron-fuses.md (+82 -0)
📝 docs/electron-observation.md (+4 -0)
📝 docs/index.md (+1 -1)
📝 docs/javascript-runtime-observation.md (+6 -0)
📝 scripts/lib/catalog-builders.mjs (+14 -2)
📝 scripts/lib/catalog-core.mjs (+1 -0)
📝 scripts/lib/test-projects.mjs (+1 -1)
📝 src/application/CapabilityInventory.ts (+5 -1)
➕ src/application/electron/ElectronFusePort.ts (+19 -0)
➕ src/application/electron/ElectronFuseService.ts (+116 -0)
📝 src/cli/electronCommands.ts (+32 -0)
📝 src/cliCommandNames.ts (+1 -0)
➕ src/composition/electronFuses.ts (+6 -0)
📝 src/contracts/javascript/electronToolContracts.ts (+17 -0)
📝 src/contracts/toolEffects.ts (+1 -0)
📝 src/contracts/toolOutputSchemaGroups.ts (+6 -0)

...and 11 more files

📄 Description

Summary

Agents can now check whether a packaged Electron app will honor --inspect, ELECTRON_RUN_AS_NODE, embedded ASAR integrity, and app-only loading before trying a runtime lane. inspect_electron_fuses / inspect-electron-fuses reads the fuse wire stored in the Electron binary. It does not launch the app, write to it, or use the network.

No issue is linked. This is a new static, read-only primitive within the existing format-inspection scope, proposed here with the implementation.

Demo

Electron fuse inspection demo

Full-quality MP4

Simulated demo rendered from real output. Every value on screen is copied from rea inspect-electron-fuses on this branch, run against Obsidian.app and Granola.app installed on my machine.

Problem and expected behavior

observe_javascript_runtime with electron-main needs the app started with Inspector enabled. When the app's EnableNodeCliInspectArguments fuse is off, Electron ignores --inspect, and the agent only finds out after a launch and a target listing that has no main-process target. On my machine that fuse is off in all three Electron apps I checked.

The same fuse wire also says whether Electron validates embedded ASAR integrity and whether it loads app code only from app.asar. That decides whether the app.asar that analyze_javascript_application reads is the only code Electron will run, and whether an integrity contradiction reported by inspect_artifact is one Electron itself would enforce. REA had no way to read any of this.

With this change, one call on a .app bundle, the Electron Framework binary, or a Windows/Linux Electron executable returns, per slice: the sentinel offset, wire version, declared length, and every fuse as {index, name, state, raw_byte}. It also returns four labeled derivations (runtime_implications): inspector arguments honored or not, ELECTRON_RUN_AS_NODE honored or not, embedded ASAR integrity enforced or not, and app code restricted to app.asar or not.

Change and scope

The reader streams the selected file once in 256 KiB windows, carrying a sentinel-sized overlap between windows. It records every sentinel per slice (up to 32) without choosing between them. Universal Mach-O files reuse readMachoImage for slice ranges, so offsets are file-absolute and each slice reports its own wire. Fuse bytes 0, 1 and r map to disabled, enabled and removed. Any other byte is unknown with the raw byte kept. Indices past the version 1 name table keep name: null, and a different wire version keeps every byte but names nothing.

A missing sentinel is reported as unknown with a limitation, never as "all fuses disabled". A truncated wire is marked complete: false. Electron compiles embedded ASAR integrity validation only for macOS and Windows, so on an ELF executable that fuse byte is kept but its derivation is unknown.

Bundle selection resolves Contents/Frameworks/Electron Framework.framework and follows Versions/Current only inside the framework directory. A file selected inside the framework (for example .../Versions/Current/Electron Framework) is re-walked from the framework root, so the same links are rejected whichever way the binary is selected, and resolved_path names the real file. Contents, Frameworks and the framework directory itself must be real directories; a symlinked one is rejected rather than followed out of the bundle.

Out of scope: changing fuse state (this tool never writes), an inspect_artifact next-probe hint (possible follow-up), and inferring Inspector access granted from inside app code (for example app.commandLine switches). That last case is called out as a limitation in every result.

Contract and boundary impact

  • Semantic owner and earliest changed stage: src/electron/ElectronFuseReader.ts (wire parsing), src/electron/ElectronFusePath.ts (selection), domain schema and derivations in src/domain/electron/electronFuses.ts, service src/application/electron/ElectronFuseService.ts.
  • Contribution scope fit and linked maintainer decision: static format inspection of caller-selected files; no execution, injection, mutation or protection recovery, so not applicable.
  • CLI and MCP/tool-catalog contract: new inspect_electron_fuses / inspect-electron-fuses with one path input; registered next to the other Electron tools, effects entry evidence (read-only), catalog builders and capability inventory updated.
  • Provider, bridge, target-format, or platform compatibility: Mach-O (thin and universal), PE, ELF. No external tool.
  • Evidence, artifact, provenance, or reconstruction contract: Evidence records selected and resolved paths, SHA-256, size and container format; derivations are labeled relationship: "derivation" and cite the fuse index and sentinel offset they came from.
  • Target selection, authority, upstream behavior, and cleanup details: reads one admitted regular file; file identity is rechecked after the scan.
  • Generated metadata, package, or installation impact: authored skill text in .agents/skills/reverse-engineer-anything/ (SKILL.md and references/runtime-observation.md); build outputs not committed.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: src/domain/electron/electronFuses.test.ts (state decoding, unknown bytes, version mismatch, missing and truncated wires, multiple sentinels, ELF ASAR applicability); src/electron/ElectronFuseProvider.test.ts (thin, universal, PE and ELF fixtures, sentinel split across read windows, framework symlink containment for bundle, framework, and direct Versions/Current/Electron Framework selection, and a symlinked framework directory); tests/acceptance/electronFusesCli.test.ts; tests/boundary/mcp/electronFusesMcp.test.ts; fixtures in tests/fixtures/electron/fuseImage.ts.
  • Base reproduction or other evidence: executed. Before this change there is no operation that reads the fuse wire.
  • User-visible CLI/MCP output: on three installed apps, the output matches an independent byte read of the same files exactly (same sentinel offsets and bytes):
App Container Wire Fuse bytes (index 0..n) Inspector args
Obsidian universal, 2 slices v1, 8 fuses 01000001 on both slices not honored
Discord universal, 2 slices v1, 9 fuses 001000011 on both slices not honored
Granola universal, 2 slices v1, 9 fuses 001011011 on both slices not honored

A full Obsidian result is 1,105 tokens (--token-count).

  • Remaining proof gaps: no real Windows or Linux Electron binary was checked; PE and ELF are covered by fixtures only.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

All on macOS 27 with Node 26 and the pinned npm 11.16.0, on this branch rebased on 8fc2043:

npm run test:focused -- tests/acceptance/electronFusesCli.test.ts tests/boundary/mcp/electronFusesMcp.test.ts \
  tests/boundary/cli/cliToolCoverage.test.ts tests/boundary/filesystem/productCatalog.test.ts \
  src/domain/electron/electronFuses.test.ts src/electron/ElectronFuseProvider.test.ts src/contracts/toolEffects.test.ts   # pass: 7 files, 40 tests
npm run check:fast             # pass
npm run docs:check             # pass
npm run verify:test-discovery  # pass, 999 files
rea inspect-electron-fuses /Applications/{Obsidian,Discord,Granola}.app   # table above

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Additive tool.
  • Real Hopper/Ghidra, browser, or OS coverage: macOS only for real binaries.
  • Package or release metadata impact: skill text and docs only.
  • Security, privacy, process, or containment review: reads one caller-selected file; symlinks are followed only inside the Electron framework directory; no process launch, writes or network.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/2399 **Author:** [@mvanhorn](https://github.com/mvanhorn) **Created:** 10/12/2026 **Status:** ✅ Merged **Merged:** 10/14/2026 **Merged by:** [@N0zoM1z0](https://github.com/N0zoM1z0) **Base:** `main` ← **Head:** `fix/electron-fuse-inspection` --- ### 📝 Commits (10+) - [`fc916b5`](https://github.com/morluto/rea/commit/fc916b568119465bcf0d6dfcbd72651de36ef3ad) feat(electron): inspect packaged Electron fuse state without execution - [`92b8195`](https://github.com/morluto/rea/commit/92b8195f9933529d18260885a2f8db299bfc83a0) fix(electron): scope ASAR enforcement and framework symlink containment - [`7902dcd`](https://github.com/morluto/rea/commit/7902dcdde07730e6752384f5de37f071b3894f74) fix(electron): contain direct framework binary selections - [`a8d4bf0`](https://github.com/morluto/rea/commit/a8d4bf07147f4f5f3c3d013c05f467d88a127482) fix(electron): reject symlinked bundle directories above the framework - [`36a38e1`](https://github.com/morluto/rea/commit/36a38e1ac03498fcbd96e0cb673ecdf3a4795992) refactor(electron): drop unused fuse type export - [`0ef7206`](https://github.com/morluto/rea/commit/0ef7206e6373c9a85053235f1b0af5485607492c) chore: merge main and preserve Electron fuse inspection - [`f7b47f8`](https://github.com/morluto/rea/commit/f7b47f8bc92014b8dd654addc5fc07f7bf3ed886) fix(electron): enforce fuse path and evidence boundaries - [`f39486d`](https://github.com/morluto/rea/commit/f39486dfaab45dbc45a3f72b7b21cc9a81dc563b) fix(electron): bind fuse reads to active framework identity - [`a13000f`](https://github.com/morluto/rea/commit/a13000fe1bfffdb5049b9b6503c9548242970d76) Merge commit '94072364c7f8410b0e86e72c912c2611572b278f' into HEAD - [`ef4c131`](https://github.com/morluto/rea/commit/ef4c131b9c401ac14babba556fc118dd758cc7fd) Merge remote-tracking branch 'origin/main' into fix/electron-fuse-inspection ### 📊 Changes **31 files changed** (+3237 additions, -9 deletions) <details> <summary>View changed files</summary> 📝 `.agents/skills/reverse-engineer-anything/SKILL.md` (+1 -1) 📝 `.agents/skills/reverse-engineer-anything/references/runtime-observation.md` (+5 -3) 📝 `docs/.vitepress/config.ts` (+1 -0) 📝 `docs/cli.md` (+1 -0) ➕ `docs/electron-fuses.md` (+82 -0) 📝 `docs/electron-observation.md` (+4 -0) 📝 `docs/index.md` (+1 -1) 📝 `docs/javascript-runtime-observation.md` (+6 -0) 📝 `scripts/lib/catalog-builders.mjs` (+14 -2) 📝 `scripts/lib/catalog-core.mjs` (+1 -0) 📝 `scripts/lib/test-projects.mjs` (+1 -1) 📝 `src/application/CapabilityInventory.ts` (+5 -1) ➕ `src/application/electron/ElectronFusePort.ts` (+19 -0) ➕ `src/application/electron/ElectronFuseService.ts` (+116 -0) 📝 `src/cli/electronCommands.ts` (+32 -0) 📝 `src/cliCommandNames.ts` (+1 -0) ➕ `src/composition/electronFuses.ts` (+6 -0) 📝 `src/contracts/javascript/electronToolContracts.ts` (+17 -0) 📝 `src/contracts/toolEffects.ts` (+1 -0) 📝 `src/contracts/toolOutputSchemaGroups.ts` (+6 -0) _...and 11 more files_ </details> ### 📄 Description ## Summary Agents can now check whether a packaged Electron app will honor `--inspect`, `ELECTRON_RUN_AS_NODE`, embedded ASAR integrity, and app-only loading before trying a runtime lane. `inspect_electron_fuses` / `inspect-electron-fuses` reads the fuse wire stored in the Electron binary. It does not launch the app, write to it, or use the network. No issue is linked. This is a new static, read-only primitive within the existing format-inspection scope, proposed here with the implementation. ## Demo ![Electron fuse inspection demo](https://raw.githubusercontent.com/mvanhorn/pr-demo-assets/main/rea/2026-10-12-023-feat-electron-fuse-inspection/preview.gif) [Full-quality MP4](https://raw.githubusercontent.com/mvanhorn/pr-demo-assets/main/rea/2026-10-12-023-feat-electron-fuse-inspection/out.mp4) Simulated demo rendered from real output. Every value on screen is copied from `rea inspect-electron-fuses` on this branch, run against Obsidian.app and Granola.app installed on my machine. ## Problem and expected behavior `observe_javascript_runtime` with `electron-main` needs the app started with Inspector enabled. When the app's `EnableNodeCliInspectArguments` fuse is off, Electron ignores `--inspect`, and the agent only finds out after a launch and a target listing that has no main-process target. On my machine that fuse is off in all three Electron apps I checked. The same fuse wire also says whether Electron validates embedded ASAR integrity and whether it loads app code only from `app.asar`. That decides whether the `app.asar` that `analyze_javascript_application` reads is the only code Electron will run, and whether an integrity contradiction reported by `inspect_artifact` is one Electron itself would enforce. REA had no way to read any of this. With this change, one call on a `.app` bundle, the Electron Framework binary, or a Windows/Linux Electron executable returns, per slice: the sentinel offset, wire version, declared length, and every fuse as `{index, name, state, raw_byte}`. It also returns four labeled derivations (`runtime_implications`): inspector arguments honored or not, `ELECTRON_RUN_AS_NODE` honored or not, embedded ASAR integrity enforced or not, and app code restricted to `app.asar` or not. ## Change and scope The reader streams the selected file once in 256 KiB windows, carrying a sentinel-sized overlap between windows. It records every sentinel per slice (up to 32) without choosing between them. Universal Mach-O files reuse `readMachoImage` for slice ranges, so offsets are file-absolute and each slice reports its own wire. Fuse bytes `0`, `1` and `r` map to disabled, enabled and removed. Any other byte is `unknown` with the raw byte kept. Indices past the version 1 name table keep `name: null`, and a different wire version keeps every byte but names nothing. A missing sentinel is reported as unknown with a limitation, never as "all fuses disabled". A truncated wire is marked `complete: false`. Electron compiles embedded ASAR integrity validation only for macOS and Windows, so on an ELF executable that fuse byte is kept but its derivation is `unknown`. Bundle selection resolves `Contents/Frameworks/Electron Framework.framework` and follows `Versions/Current` only inside the framework directory. A file selected inside the framework (for example `.../Versions/Current/Electron Framework`) is re-walked from the framework root, so the same links are rejected whichever way the binary is selected, and `resolved_path` names the real file. `Contents`, `Frameworks` and the framework directory itself must be real directories; a symlinked one is rejected rather than followed out of the bundle. Out of scope: changing fuse state (this tool never writes), an `inspect_artifact` next-probe hint (possible follow-up), and inferring Inspector access granted from inside app code (for example `app.commandLine` switches). That last case is called out as a limitation in every result. ## Contract and boundary impact - Semantic owner and earliest changed stage: `src/electron/ElectronFuseReader.ts` (wire parsing), `src/electron/ElectronFusePath.ts` (selection), domain schema and derivations in `src/domain/electron/electronFuses.ts`, service `src/application/electron/ElectronFuseService.ts`. - Contribution scope fit and linked maintainer decision: static format inspection of caller-selected files; no execution, injection, mutation or protection recovery, so not applicable. - CLI and MCP/tool-catalog contract: new `inspect_electron_fuses` / `inspect-electron-fuses` with one `path` input; registered next to the other Electron tools, effects entry `evidence` (read-only), catalog builders and capability inventory updated. - Provider, bridge, target-format, or platform compatibility: Mach-O (thin and universal), PE, ELF. No external tool. - Evidence, artifact, provenance, or reconstruction contract: Evidence records selected and resolved paths, SHA-256, size and container format; derivations are labeled `relationship: "derivation"` and cite the fuse index and sentinel offset they came from. - Target selection, authority, upstream behavior, and cleanup details: reads one admitted regular file; file identity is rechecked after the scan. - Generated metadata, package, or installation impact: authored skill text in `.agents/skills/reverse-engineer-anything/` (SKILL.md and `references/runtime-observation.md`); build outputs not committed. Expected effects: - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `src/domain/electron/electronFuses.test.ts` (state decoding, unknown bytes, version mismatch, missing and truncated wires, multiple sentinels, ELF ASAR applicability); `src/electron/ElectronFuseProvider.test.ts` (thin, universal, PE and ELF fixtures, sentinel split across read windows, framework symlink containment for bundle, framework, and direct `Versions/Current/Electron Framework` selection, and a symlinked framework directory); `tests/acceptance/electronFusesCli.test.ts`; `tests/boundary/mcp/electronFusesMcp.test.ts`; fixtures in `tests/fixtures/electron/fuseImage.ts`. - Base reproduction or other evidence: executed. Before this change there is no operation that reads the fuse wire. - User-visible CLI/MCP output: on three installed apps, the output matches an independent byte read of the same files exactly (same sentinel offsets and bytes): | App | Container | Wire | Fuse bytes (index 0..n) | Inspector args | | --- | --- | --- | --- | --- | | Obsidian | universal, 2 slices | v1, 8 fuses | `01000001` on both slices | not honored | | Discord | universal, 2 slices | v1, 9 fuses | `001000011` on both slices | not honored | | Granola | universal, 2 slices | v1, 9 fuses | `001011011` on both slices | not honored | A full Obsidian result is 1,105 tokens (`--token-count`). - Remaining proof gaps: no real Windows or Linux Electron binary was checked; PE and ELF are covered by fixtures only. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed All on macOS 27 with Node 26 and the pinned npm 11.16.0, on this branch rebased on `8fc2043`: ``` npm run test:focused -- tests/acceptance/electronFusesCli.test.ts tests/boundary/mcp/electronFusesMcp.test.ts \ tests/boundary/cli/cliToolCoverage.test.ts tests/boundary/filesystem/productCatalog.test.ts \ src/domain/electron/electronFuses.test.ts src/electron/ElectronFuseProvider.test.ts src/contracts/toolEffects.test.ts # pass: 7 files, 40 tests npm run check:fast # pass npm run docs:check # pass npm run verify:test-discovery # pass, 999 files rea inspect-electron-fuses /Applications/{Obsidian,Discord,Granola}.app # table above ``` ## Compatibility, safety, and release - Breaking changes or migration steps: none. Additive tool. - Real Hopper/Ghidra, browser, or OS coverage: macOS only for real binaries. - Package or release metadata impact: skill text and docs only. - Security, privacy, process, or containment review: reads one caller-selected file; symlinks are followed only inside the Electron framework directory; no process launch, writes or network. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:04:45 +00:00
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#5200
No description provided.