[GH-ISSUE #4393] [Bug] capture_electron_scenario active hook does not block or record shell.showItemInFolder and shell.writeShortcutLink #1746

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

Originally created by @keeponlight on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4393

Before submitting

  • I searched the open and closed issues for a related report. (Follow-up to Cluster F #4330; cross-references #3452, #3474, and PR #3559 review notes)
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Process capture or runtime observation

Observed behavior

In scripts/electron-active-hook-boundaries.cjs (lines 346–359), patchShell only instruments asynchronous URL/file opening APIs (openExternal and openPath, with trashItem tracked separately in #3474 / PR #3559):

  const patchShell = (shell) => {
    if (shell === null || shell === undefined) return;
    for (const method of ["openExternal", "openPath"]) {
      const original = shell[method];
      if (typeof original !== "function") continue;
      shell[method] = function blockedShellCall(...args) {
        recordRuntime("shell-attempt", method, "blocked", {
          argument_shape_values: args,
          error: true,
        });
        return Promise.reject(new Error("External shell effects are blocked"));
      };
    }
  };

Synchronous host OS and filesystem shell APIs provided by Electron are omitted from patchShell:

  1. shell.showItemInFolder(fullPath: string): void: Triggers host OS desktop integration by launching the native file manager (Finder on macOS, Explorer on Windows, desktop file manager on Linux) and selecting the given path.
  2. shell.writeShortcutLink(shortcutPath: string, [operation: 'create' | 'update' | 'replace',] options: ShortcutDetails): boolean: Directly creates or overwrites Windows .lnk shortcut files on the host filesystem.

When an inspected target calls shell.showItemInFolder(...) or shell.writeShortcutLink(...) during capture_electron_scenario, both calls bypass the active boundary hook entirely, execute their underlying native implementations against the host OS, and produce 0 shell-attempt events in the capture snapshot.

Reproduction

Executed harness verification against current origin/main commit 2c7583599 (Node v24.14.1, macOS Darwin 24.6.0 arm64):

node -e '
const { createElectronActiveBoundaryPatches } = require("./scripts/electron-active-hook-boundaries.cjs");
const recorded = [];
const patches = createElectronActiveBoundaryPatches({
  identity: () => "shell",
  patchEmitter: () => {},
  processType: () => "browser",
  recordIpc: () => {},
  recordRuntime: (kind, event, phase, meta) => recorded.push({ kind, event, phase, meta }),
  shape: (x) => typeof x,
});

let showItemExecuted = false;
let writeShortcutExecuted = false;

const shell = {
  openExternal: () => Promise.resolve(),
  openPath: () => Promise.resolve(),
  showItemInFolder: () => {
    showItemExecuted = true;
    return undefined;
  },
  writeShortcutLink: () => {
    writeShortcutExecuted = true;
    return true;
  },
};

patches.patchShell(shell);

shell.openExternal("https://example.invalid").catch(() => {});
const showResult = shell.showItemInFolder("/host/path/file.txt");
const writeResult = shell.writeShortcutLink("C:\\Users\\user\\Desktop\\app.lnk", "create", { target: "C:\\evil.exe" });

console.log("showItemExecuted:", showItemExecuted, "showResult:", showResult);
console.log("writeShortcutExecuted:", writeShortcutExecuted, "writeResult:", writeResult);
console.log("recorded events:", recorded.map((e) => `${e.kind}:${e.event}:${e.phase}`));
'

Output:

showItemExecuted: true showResult: undefined
writeShortcutExecuted: true writeResult: true
recorded events: [ 'shell-attempt:openExternal:blocked' ]

Both methods ran directly through to their underlying implementations (showItemExecuted: true, writeShortcutExecuted: true), returned native values, and emitted 0 timeline events.

Expected behavior

Per docs/electron-observation.md (lines 103–108):

"IPC channels and argument-shape metadata retain their observed lengths; payload values are never retained. ... The active hook blocks and records external shell/navigation, permission, download, popup, updater, and OS-integration effects."

  1. Pre-retention boundary recording vs. post-retention timeline projection:
    • In scripts/electron-active-hook-boundaries.cjs (patchShell), intercepted calls to showItemInFolder and writeShortcutLink must invoke recordRuntime("shell-attempt", method, "blocked", { argument_shape_values: args, error: true }).
    • Downstream in scripts/electron-active-hook.cjs (record, lines 193–221 and 293–295), argument_shape_values is purely a pre-retention input: record projects each argument through shape(argument) into retainedEvent.argument_shapes (e.g., ["string"] for showItemInFolder(fullPath), and ["string", "object"] or ["string", "string", "object"] for writeShortcutLink(shortcutPath, [operation], options)) and deletes argument_shape_values so raw filesystem paths and shortcut option payloads are never retained in __reaElectronActiveSnapshot().
  2. Synchronous return contracts (distinct from Promise-returning shell methods):
    Unlike openExternal, openPath, and trashItem (which return a Promise and reject with new Error("External shell effects are blocked")), showItemInFolder and writeShortcutLink are synchronous in Electron:
    • shell.showItemInFolder(fullPath) has a synchronous void signature: when blocked, it must not invoke the underlying native implementation and must synchronously return undefined (never returning a rejected Promise or throwing an uncaught exception).
    • shell.writeShortcutLink(shortcutPath, [operation], options) has a synchronous boolean signature: when blocked, it must not create or modify host .lnk files and must synchronously return false (matching Electron's boolean failure return contract, analogous to how patchApplicationEffects returns false for setAsDefaultProtocolClient and moveToApplicationsFolder).

Impact and scope

  • Evidence Integrity: Host filesystem shortcut creations and file-manager reveal actions performed by inspected applications leave no trace on the active capture timeline.
  • Process Isolation / Containment: Targets running under automated scenario capture can pop native file-manager windows on the host desktop (Finder/Explorer) or write .lnk shortcuts on Windows disk storage without interception.
  • Cluster Tracking: Identified under Cluster F (Electron Active Hook Host Filesystem Mutation Bypass) in tracking audit #4330; directly follows up on #3452 and #3474 (PR #3559).

REA version

Source 2c7583599b9ac1c847bd77b1940930fd8d4594c7 (origin/main)

Runtime and platform

Node 24.14.1; macOS Darwin 24.6.0 arm64 (also impacts Windows for writeShortcutLink and all desktop platforms for showItemInFolder)

Originally created by @keeponlight on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4393 ### Before submitting - [x] I searched the open and closed issues for a related report. (Follow-up to Cluster F #4330; cross-references #3452, #3474, and PR #3559 review notes) - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Process capture or runtime observation ### Observed behavior In `scripts/electron-active-hook-boundaries.cjs` (lines 346–359), `patchShell` only instruments asynchronous URL/file opening APIs (`openExternal` and `openPath`, with `trashItem` tracked separately in #3474 / PR #3559): ```javascript const patchShell = (shell) => { if (shell === null || shell === undefined) return; for (const method of ["openExternal", "openPath"]) { const original = shell[method]; if (typeof original !== "function") continue; shell[method] = function blockedShellCall(...args) { recordRuntime("shell-attempt", method, "blocked", { argument_shape_values: args, error: true, }); return Promise.reject(new Error("External shell effects are blocked")); }; } }; ``` Synchronous host OS and filesystem shell APIs provided by Electron are omitted from `patchShell`: 1. `shell.showItemInFolder(fullPath: string): void`: Triggers host OS desktop integration by launching the native file manager (Finder on macOS, Explorer on Windows, desktop file manager on Linux) and selecting the given path. 2. `shell.writeShortcutLink(shortcutPath: string, [operation: 'create' | 'update' | 'replace',] options: ShortcutDetails): boolean`: Directly creates or overwrites Windows `.lnk` shortcut files on the host filesystem. When an inspected target calls `shell.showItemInFolder(...)` or `shell.writeShortcutLink(...)` during `capture_electron_scenario`, both calls bypass the active boundary hook entirely, execute their underlying native implementations against the host OS, and produce `0` `shell-attempt` events in the capture snapshot. ### Reproduction Executed harness verification against current `origin/main` commit `2c7583599` (Node v24.14.1, macOS Darwin 24.6.0 arm64): ```bash node -e ' const { createElectronActiveBoundaryPatches } = require("./scripts/electron-active-hook-boundaries.cjs"); const recorded = []; const patches = createElectronActiveBoundaryPatches({ identity: () => "shell", patchEmitter: () => {}, processType: () => "browser", recordIpc: () => {}, recordRuntime: (kind, event, phase, meta) => recorded.push({ kind, event, phase, meta }), shape: (x) => typeof x, }); let showItemExecuted = false; let writeShortcutExecuted = false; const shell = { openExternal: () => Promise.resolve(), openPath: () => Promise.resolve(), showItemInFolder: () => { showItemExecuted = true; return undefined; }, writeShortcutLink: () => { writeShortcutExecuted = true; return true; }, }; patches.patchShell(shell); shell.openExternal("https://example.invalid").catch(() => {}); const showResult = shell.showItemInFolder("/host/path/file.txt"); const writeResult = shell.writeShortcutLink("C:\\Users\\user\\Desktop\\app.lnk", "create", { target: "C:\\evil.exe" }); console.log("showItemExecuted:", showItemExecuted, "showResult:", showResult); console.log("writeShortcutExecuted:", writeShortcutExecuted, "writeResult:", writeResult); console.log("recorded events:", recorded.map((e) => `${e.kind}:${e.event}:${e.phase}`)); ' ``` Output: ```text showItemExecuted: true showResult: undefined writeShortcutExecuted: true writeResult: true recorded events: [ 'shell-attempt:openExternal:blocked' ] ``` Both methods ran directly through to their underlying implementations (`showItemExecuted: true`, `writeShortcutExecuted: true`), returned native values, and emitted `0` timeline events. ### Expected behavior Per `docs/electron-observation.md` (lines 103–108): > "IPC channels and argument-shape metadata retain their observed lengths; payload values are never retained. ... The active hook blocks and records external shell/navigation, permission, download, popup, updater, and OS-integration effects." 1. **Pre-retention boundary recording vs. post-retention timeline projection**: - In `scripts/electron-active-hook-boundaries.cjs` (`patchShell`), intercepted calls to `showItemInFolder` and `writeShortcutLink` must invoke `recordRuntime("shell-attempt", method, "blocked", { argument_shape_values: args, error: true })`. - Downstream in `scripts/electron-active-hook.cjs` (`record`, lines 193–221 and 293–295), `argument_shape_values` is purely a pre-retention input: `record` projects each argument through `shape(argument)` into `retainedEvent.argument_shapes` (e.g., `["string"]` for `showItemInFolder(fullPath)`, and `["string", "object"]` or `["string", "string", "object"]` for `writeShortcutLink(shortcutPath, [operation], options)`) and deletes `argument_shape_values` so raw filesystem paths and shortcut option payloads are never retained in `__reaElectronActiveSnapshot()`. 2. **Synchronous return contracts (distinct from Promise-returning `shell` methods)**: Unlike `openExternal`, `openPath`, and `trashItem` (which return a `Promise` and reject with `new Error("External shell effects are blocked")`), `showItemInFolder` and `writeShortcutLink` are synchronous in Electron: - `shell.showItemInFolder(fullPath)` has a synchronous `void` signature: when blocked, it must not invoke the underlying native implementation and must synchronously return `undefined` (never returning a rejected `Promise` or throwing an uncaught exception). - `shell.writeShortcutLink(shortcutPath, [operation], options)` has a synchronous `boolean` signature: when blocked, it must not create or modify host `.lnk` files and must synchronously return `false` (matching Electron's boolean failure return contract, analogous to how `patchApplicationEffects` returns `false` for `setAsDefaultProtocolClient` and `moveToApplicationsFolder`). ### Impact and scope - **Evidence Integrity**: Host filesystem shortcut creations and file-manager reveal actions performed by inspected applications leave no trace on the active capture timeline. - **Process Isolation / Containment**: Targets running under automated scenario capture can pop native file-manager windows on the host desktop (Finder/Explorer) or write `.lnk` shortcuts on Windows disk storage without interception. - **Cluster Tracking**: Identified under Cluster F (Electron Active Hook Host Filesystem Mutation Bypass) in tracking audit #4330; directly follows up on #3452 and #3474 (PR #3559). ### REA version Source `2c7583599b9ac1c847bd77b1940930fd8d4594c7` (`origin/main`) ### Runtime and platform Node 24.14.1; macOS Darwin 24.6.0 arm64 (also impacts Windows for `writeShortcutLink` and all desktop platforms for `showItemInFolder`)
Author
Owner

@xu91102 commented on GitHub (Oct 16, 2026):

Reproduced on current main d72d856f58d4747c5eb4307426e80d20934e017c using Node 24.18.0 / macOS arm64. A fresh Node subprocess loaded the complete production active hook and its snapshot projection against a controlled Electron module. showItemInFolder and both shortcut-call forms reached the underlying controlled methods (3 calls total); writeShortcutLink returned true, and none of the three attempts appeared in the timeline. The asynchronous openExternal control was blocked and recorded. hook_error stayed false.

This is production-hook boundary execution; I did not execute native Electron, open Finder/Explorer, or write a Windows shortcut. I inspected #3559 at 553b816d97b489bf038c026979d4ce5a9af5e7b1: its change covers asynchronous trashItem, not these synchronous methods. I am preparing a separate focused fix preserving void/boolean return contracts and shape-only evidence, with the full-hook subprocess as the regression.

Update: reproduced again on main 781d5751ad18b25a1fe1ffa8c6035388be8c3283. Proposed two-file fix blocks both synchronous methods at the existing shell boundary, preserving undefined/false return shapes, optional-method guards and argument-shape-only evidence. The full hook subprocess file passes all 5 cases. Actual macOS arm64 Electron 37.10.3 reveal blocking passed through focused capture, the public CLI and stdio MCP, with completed actions and terminated-owned-process cleanup. Native Windows shortcut execution was not run.

Static checks, docs and discovery passed. Broader validation is incomplete: one existing static CLI/MCP early-return case times out at its unchanged 30-second limit on both clean main and the patch; the rich verify:electron capture fails with execution-context destruction even with the unmodified repository application. The clean-main full-verifier control failed earlier at loopback discovery, so it does not establish the cause of the later capture failure. Independent Standards and Spec source review passed; neither replaces those missing full-lane results.

The patch is committed and pushed. No upstream PR or CI exists yet: an ordinary same-account Draft creation for the other independently prepared fix returned CreatePullRequest permission denied, so I did not repeat the same creation attempt here. Human readiness remains pending.

<!-- gh-comment-id:6211777135 --> @xu91102 commented on GitHub (Oct 16, 2026): Reproduced on current main `d72d856f58d4747c5eb4307426e80d20934e017c` using Node 24.18.0 / macOS arm64. A fresh Node subprocess loaded the complete production active hook and its snapshot projection against a controlled Electron module. `showItemInFolder` and both shortcut-call forms reached the underlying controlled methods (3 calls total); `writeShortcutLink` returned `true`, and none of the three attempts appeared in the timeline. The asynchronous `openExternal` control was blocked and recorded. `hook_error` stayed false. This is production-hook boundary execution; I did not execute native Electron, open Finder/Explorer, or write a Windows shortcut. I inspected #3559 at `553b816d97b489bf038c026979d4ce5a9af5e7b1`: its change covers asynchronous `trashItem`, not these synchronous methods. I am preparing a separate focused fix preserving `void`/`boolean` return contracts and shape-only evidence, with the full-hook subprocess as the regression. Update: reproduced again on main `781d5751ad18b25a1fe1ffa8c6035388be8c3283`. Proposed [two-file fix](https://github.com/xu91102/rea/commit/2141b4a03576352dd1ab95fc2f3a25c07946844b) blocks both synchronous methods at the existing shell boundary, preserving undefined/false return shapes, optional-method guards and argument-shape-only evidence. The full hook subprocess file passes all 5 cases. Actual macOS arm64 Electron 37.10.3 reveal blocking passed through focused capture, the public CLI and stdio MCP, with completed actions and `terminated-owned-process` cleanup. Native Windows shortcut execution was not run. Static checks, docs and discovery passed. Broader validation is incomplete: one existing static CLI/MCP early-return case times out at its unchanged 30-second limit on both clean main and the patch; the rich `verify:electron` capture fails with execution-context destruction even with the unmodified repository application. The clean-main full-verifier control failed earlier at loopback discovery, so it does not establish the cause of the later capture failure. Independent Standards and Spec source review passed; neither replaces those missing full-lane results. The patch is committed and pushed. No upstream PR or CI exists yet: an ordinary same-account Draft creation for the other independently prepared fix returned `CreatePullRequest` permission denied, so I did not repeat the same creation attempt here. Human readiness remains 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#1746
No description provided.