[GH-ISSUE #8891] [Bug] capabilities omits inspect_macho's temporary filesystem-write requirement #7783

Open
opened 2026-11-04 13:11:25 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Nov 4, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8891

Before submitting

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

Area

Provider or capability discovery

Observed behavior

On the source revision below, node scripts/rea.mjs capabilities --format json reports only launches_process for the group containing inspect_macho. However, inspecting a valid Mach-O named app(foo) creates a private temporary directory and symlink. An external filesystem observer saw the symlink point to that target during the unmodified CLI call; successful command provenance also contains the private alias, which was removed afterward.

The missing dependency is observable without a filesystem watcher: after a writable plain-file warmup, making only the temporary parent unwritable leaves a byte-identical plain-name target inspectable, while app(foo) fails with Unable to create a private otool operand. Restoring write permission restores success.

Reproduction

Use macOS with Apple's native tools and Node v24.22.1. Start from a checkout at c3647ade1e557c4179a8b825a37e0c2193cd2526 with locked dependencies and the compiled runtime prepared. Set and export WORK to an empty absolute scratch directory outside the checkout, then run the following from its root. This is the script executed for the controlled comparison. The initial writable plain-file call prepares the independent native-command helper before changing temporary-directory permissions.

set -eu
: "${WORK:?Set WORK to an empty absolute scratch directory}"
mkdir -p "$WORK/home" "$WORK/temp" "$WORK/config" "$WORK/data" "$WORK/cache" "$WORK/state" "$WORK/runtime"
export HOME="$WORK/home" TMPDIR="$WORK/temp"
export XDG_CONFIG_HOME="$WORK/config" XDG_DATA_HOME="$WORK/data"
export XDG_CACHE_HOME="$WORK/cache" XDG_STATE_HOME="$WORK/state" XDG_RUNTIME_DIR="$WORK/runtime"
printf 'int main(void) { return 0; }\n' > "$WORK/main.c"
/usr/bin/clang "$WORK/main.c" -o "$WORK/plain"
cp "$WORK/plain" "$WORK/app(foo)"
node scripts/rea.mjs capabilities --format json > "$WORK/capabilities.json"
node -e 'const f=require("node:fs"); const x=JSON.parse(f.readFileSync(process.env.WORK+"/capabilities.json")); const g=x.capabilities.find(c=>c.provider.id==="native-macos").groups.find(g=>g.operations.includes("inspect_macho")); console.log("inspect_macho effects:",JSON.stringify(g.effects));'
node scripts/rea.mjs inspect-macho "$WORK/plain" --format json > "$WORK/warmup.json"
echo 'writable temp, plain warmup: exit 0'
chmod 500 "$TMPDIR"
trap 'chmod 700 "$TMPDIR"' EXIT
node scripts/rea.mjs inspect-macho "$WORK/plain" --format json > "$WORK/plain.json"
echo 'unwritable temp, plain: exit 0'
if node scripts/rea.mjs inspect-macho "$WORK/app(foo)" --format json > "$WORK/denied.json"; then
  echo 'unexpected success'; exit 1
fi
node -e 'const f=require("node:fs"); const x=JSON.parse(f.readFileSync(process.env.WORK+"/denied.json")); console.log("unwritable temp, app(foo):",x.code,"/",x.details.diagnostics.reason);'
chmod 700 "$TMPDIR"
node scripts/rea.mjs inspect-macho "$WORK/app(foo)" --format json > "$WORK/writable.json"
echo 'writable temp, app(foo): exit 0'
node -e 'const f=require("node:fs"); const x=JSON.parse(f.readFileSync(process.env.WORK+"/writable.json")); const operands=x.normalized_result.provenance.map(p=>p.command.at(-1)); console.log("private alias used:", operands.some(p=>p.includes("rea-macho-operand-")&&p.endsWith("/artifact.macho"))); console.log("temporary parent empty:",f.readdirSync(process.env.TMPDIR).length===0);'
cmp "$WORK/plain" "$WORK/app(foo)"
echo 'target bytes equal to plain control: true'

The script completed with the output shown below. A separate text-file negative control was rejected as target_unavailable/unsupported_target before any private operand was created.

Expected behavior

Capability discovery should include may_write_filesystem for inspect_macho because at least one supported filename requires temporary filesystem writes. mutates_artifact should remain absent: the target itself is not modified. An unwritable temporary parent may still legitimately prevent this branch from running; discovery should disclose the dependency before execution.

Expected source: at the reported revision, tool-design.md requires truthful filesystem effects. toolEffects.ts explicitly defines possible effects to include temporary work across supported inputs. The existing signature-inspection declaration includes its private temporary write.

Impact and scope

P2: callers cannot discover this temporary write requirement and can select an environment in which the demonstrated supported filename fails. This is a bounded metadata defect, with writable private temporary storage demonstrated as a safe workaround. The plain-name control succeeds in the same unwritable environment after helper preparation.

The original target bytes remained unchanged, the successful call returned analysis facts, and its alias was removed. No security-policy bypass, evidence corruption, snapshot replay failure or widespread downstream integration impact was observed. The exercised input is a locally compiled thin arm64 Mach-O whose basename ends in a parenthesized group; other platforms and all filename spellings were not tested.

REA version

Source revision c3647ade1e557c4179a8b825a37e0c2193cd2526; package and rea --version report 6.8.0. The npm-published 6.8.0 artifact predates the alias workaround and differs from this source revision. This report concerns the source behavior; it does not claim the exact alias-induced mismatch was reproduced in that published artifact.

Runtime and platform

Node.js v24.22.1; macOS 27.2; Darwin arm64. Real Apple tools selected through Xcode; no fake provider or injected command runner. HOME, XDG directories, temporary storage and outputs were isolated in the scratch directory.

Target and artifact context

A locally compiled Mach-O arm64 executable containing int main(void) { return 0; }, copied unchanged to plain and app(foo). The inspection did not execute the target. The two files remained byte-identical after the comparison.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

Actual output from the reproduction script:

inspect_macho effects: ["launches_process"]
writable temp, plain warmup: exit 0
unwritable temp, plain: exit 0
unwritable temp, app(foo): execution_failure / Unable to create a private otool operand
writable temp, app(foo): exit 0
private alias used: true
temporary parent empty: true
target bytes equal to plain control: true

The parenthesized denied-storage result contains code: "execution_failure", details.provider_id: "native-macos", details.operation: "inspect_macho", and details.diagnostics.reason: "Unable to create a private otool operand". Its writable result contains successful native command provenance whose operand ends in rea-macho-operand-<generated>/artifact.macho. A separate external observer directly read that symlink while the CLI was running and confirmed it pointed to the selected file; it did not modify or instrument REA.

At the source revision, NativeMacOSProviderMetadata.ts:58-61 excludes inspect_macho from filesystem writes, while NativeMacOSProvider.ts:1332-1334 selects its parenthesized-basename branch and lines 1440-1467 allocate the private root and symlink. The focused operand lifecycle test file also passed all seven tests; those simulated-runner tests are corroboration, not the basis for the CLI reproduction.

Typed outcome or recovery expected

Include may_write_filesystem in the capability group for inspect_macho while preserving the nonmutating target behavior and existing temporary-resource cleanup. Do not turn the denied-storage control into a success requirement. A caller should know writable temporary storage may be needed before choosing this operation.

Analysis or suspected cause

Source-derived: the native provider's effect declaration explicitly enumerates writing operations, but the later private Mach-O operand branch is not included. The canonical sibling declaration also assigns inspect_macho the nonwriting nativeEvidence effects at toolEffects.ts:617. This is the same missing temporary-write fact, not a separate execution defect.

The capability projection emits only enabled effects, explaining the real CLI omission. Although snapshot retention also examines the provider boolean, actual replay has further profile guards. No cache failure is claimed here.

Additional context

The latest npm version check returned 6.8.0. Its real CLI succeeded on plain but failed on app(foo) with the older shortened-operand otool error and created no alias. That published result is the already-reported filename problem in closed #7556. Merged PR #7831 adds the alias workaround; the current report concerns discovery of its resulting temporary write requirement.

Fetched upstream main at 2d16a40a491067ef0bbe541feef2feeb5f3110ad retains the same relevant descriptor and alias branch by source comparison; current-main runtime execution was not performed. Closed #5423 and closed PR #5430 concern the analogous signature-snapshot effect omission, whose closure rationale confirms the signature-specific declaration was corrected. Searches across open and closed issues and all PR states found no open exact duplicate for the Mach-O alias effect. No fix or post-fix restoration was tested.

Originally created by @coygeek on GitHub (Nov 4, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8891 ### 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 Provider or capability discovery ### Observed behavior On the source revision below, `node scripts/rea.mjs capabilities --format json` reports only `launches_process` for the group containing `inspect_macho`. However, inspecting a valid Mach-O named `app(foo)` creates a private temporary directory and symlink. An external filesystem observer saw the symlink point to that target during the unmodified CLI call; successful command provenance also contains the private alias, which was removed afterward. The missing dependency is observable without a filesystem watcher: after a writable plain-file warmup, making only the temporary parent unwritable leaves a byte-identical plain-name target inspectable, while `app(foo)` fails with `Unable to create a private otool operand`. Restoring write permission restores success. ### Reproduction Use macOS with Apple's native tools and Node v24.22.1. Start from a checkout at `c3647ade1e557c4179a8b825a37e0c2193cd2526` with locked dependencies and the compiled runtime prepared. Set and export `WORK` to an empty absolute scratch directory outside the checkout, then run the following from its root. This is the script executed for the controlled comparison. The initial writable plain-file call prepares the independent native-command helper before changing temporary-directory permissions. ```sh set -eu : "${WORK:?Set WORK to an empty absolute scratch directory}" mkdir -p "$WORK/home" "$WORK/temp" "$WORK/config" "$WORK/data" "$WORK/cache" "$WORK/state" "$WORK/runtime" export HOME="$WORK/home" TMPDIR="$WORK/temp" export XDG_CONFIG_HOME="$WORK/config" XDG_DATA_HOME="$WORK/data" export XDG_CACHE_HOME="$WORK/cache" XDG_STATE_HOME="$WORK/state" XDG_RUNTIME_DIR="$WORK/runtime" printf 'int main(void) { return 0; }\n' > "$WORK/main.c" /usr/bin/clang "$WORK/main.c" -o "$WORK/plain" cp "$WORK/plain" "$WORK/app(foo)" node scripts/rea.mjs capabilities --format json > "$WORK/capabilities.json" node -e 'const f=require("node:fs"); const x=JSON.parse(f.readFileSync(process.env.WORK+"/capabilities.json")); const g=x.capabilities.find(c=>c.provider.id==="native-macos").groups.find(g=>g.operations.includes("inspect_macho")); console.log("inspect_macho effects:",JSON.stringify(g.effects));' node scripts/rea.mjs inspect-macho "$WORK/plain" --format json > "$WORK/warmup.json" echo 'writable temp, plain warmup: exit 0' chmod 500 "$TMPDIR" trap 'chmod 700 "$TMPDIR"' EXIT node scripts/rea.mjs inspect-macho "$WORK/plain" --format json > "$WORK/plain.json" echo 'unwritable temp, plain: exit 0' if node scripts/rea.mjs inspect-macho "$WORK/app(foo)" --format json > "$WORK/denied.json"; then echo 'unexpected success'; exit 1 fi node -e 'const f=require("node:fs"); const x=JSON.parse(f.readFileSync(process.env.WORK+"/denied.json")); console.log("unwritable temp, app(foo):",x.code,"/",x.details.diagnostics.reason);' chmod 700 "$TMPDIR" node scripts/rea.mjs inspect-macho "$WORK/app(foo)" --format json > "$WORK/writable.json" echo 'writable temp, app(foo): exit 0' node -e 'const f=require("node:fs"); const x=JSON.parse(f.readFileSync(process.env.WORK+"/writable.json")); const operands=x.normalized_result.provenance.map(p=>p.command.at(-1)); console.log("private alias used:", operands.some(p=>p.includes("rea-macho-operand-")&&p.endsWith("/artifact.macho"))); console.log("temporary parent empty:",f.readdirSync(process.env.TMPDIR).length===0);' cmp "$WORK/plain" "$WORK/app(foo)" echo 'target bytes equal to plain control: true' ``` The script completed with the output shown below. A separate text-file negative control was rejected as `target_unavailable`/`unsupported_target` before any private operand was created. ### Expected behavior Capability discovery should include `may_write_filesystem` for `inspect_macho` because at least one supported filename requires temporary filesystem writes. `mutates_artifact` should remain absent: the target itself is not modified. An unwritable temporary parent may still legitimately prevent this branch from running; discovery should disclose the dependency before execution. Expected source: at the reported revision, [tool-design.md](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/docs/tool-design.md#L172) requires truthful filesystem effects. [toolEffects.ts](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/contracts/toolEffects.ts#L1) explicitly defines possible effects to include temporary work across supported inputs. The existing signature-inspection declaration includes its private temporary write. ### Impact and scope P2: callers cannot discover this temporary write requirement and can select an environment in which the demonstrated supported filename fails. This is a bounded metadata defect, with writable private temporary storage demonstrated as a safe workaround. The plain-name control succeeds in the same unwritable environment after helper preparation. The original target bytes remained unchanged, the successful call returned analysis facts, and its alias was removed. No security-policy bypass, evidence corruption, snapshot replay failure or widespread downstream integration impact was observed. The exercised input is a locally compiled thin arm64 Mach-O whose basename ends in a parenthesized group; other platforms and all filename spellings were not tested. ### REA version Source revision `c3647ade1e557c4179a8b825a37e0c2193cd2526`; package and `rea --version` report `6.8.0`. The npm-published 6.8.0 artifact predates the alias workaround and differs from this source revision. This report concerns the source behavior; it does not claim the exact alias-induced mismatch was reproduced in that published artifact. ### Runtime and platform Node.js `v24.22.1`; macOS `27.2`; Darwin arm64. Real Apple tools selected through Xcode; no fake provider or injected command runner. HOME, XDG directories, temporary storage and outputs were isolated in the scratch directory. ### Target and artifact context A locally compiled Mach-O arm64 executable containing `int main(void) { return 0; }`, copied unchanged to `plain` and `app(foo)`. The inspection did not execute the target. The two files remained byte-identical after the comparison. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence Actual output from the reproduction script: ```text inspect_macho effects: ["launches_process"] writable temp, plain warmup: exit 0 unwritable temp, plain: exit 0 unwritable temp, app(foo): execution_failure / Unable to create a private otool operand writable temp, app(foo): exit 0 private alias used: true temporary parent empty: true target bytes equal to plain control: true ``` The parenthesized denied-storage result contains `code: "execution_failure"`, `details.provider_id: "native-macos"`, `details.operation: "inspect_macho"`, and `details.diagnostics.reason: "Unable to create a private otool operand"`. Its writable result contains successful native command provenance whose operand ends in `rea-macho-operand-<generated>/artifact.macho`. A separate external observer directly read that symlink while the CLI was running and confirmed it pointed to the selected file; it did not modify or instrument REA. At the source revision, [NativeMacOSProviderMetadata.ts:58-61](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/native/NativeMacOSProviderMetadata.ts#L58) excludes `inspect_macho` from filesystem writes, while [NativeMacOSProvider.ts:1332-1334](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/native/NativeMacOSProvider.ts#L1332) selects its parenthesized-basename branch and [lines 1440-1467](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/native/NativeMacOSProvider.ts#L1440) allocate the private root and symlink. The focused operand lifecycle test file also passed all seven tests; those simulated-runner tests are corroboration, not the basis for the CLI reproduction. ### Typed outcome or recovery expected Include `may_write_filesystem` in the capability group for `inspect_macho` while preserving the nonmutating target behavior and existing temporary-resource cleanup. Do not turn the denied-storage control into a success requirement. A caller should know writable temporary storage may be needed before choosing this operation. ### Analysis or suspected cause Source-derived: the native provider's effect declaration explicitly enumerates writing operations, but the later private Mach-O operand branch is not included. The canonical sibling declaration also assigns `inspect_macho` the nonwriting `nativeEvidence` effects at [toolEffects.ts:617](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/contracts/toolEffects.ts#L617). This is the same missing temporary-write fact, not a separate execution defect. The [capability projection](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/application/binary/BinarySessionStatus.ts#L456) emits only enabled effects, explaining the real CLI omission. Although [snapshot retention](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/application/binary/AnalysisSnapshotCache.ts#L123) also examines the provider boolean, actual replay has further profile guards. No cache failure is claimed here. ### Additional context The latest npm version check returned 6.8.0. Its real CLI succeeded on `plain` but failed on `app(foo)` with the older shortened-operand `otool` error and created no alias. That published result is the already-reported filename problem in closed [#7556](https://github.com/morluto/rea/issues/7556). Merged [PR #7831](https://github.com/morluto/rea/pull/7831) adds the alias workaround; the current report concerns discovery of its resulting temporary write requirement. Fetched upstream main at `2d16a40a491067ef0bbe541feef2feeb5f3110ad` retains the same relevant descriptor and alias branch by source comparison; current-main runtime execution was not performed. Closed [#5423](https://github.com/morluto/rea/issues/5423) and closed [PR #5430](https://github.com/morluto/rea/pull/5430) concern the analogous signature-snapshot effect omission, whose closure rationale confirms the signature-specific declaration was corrected. Searches across open and closed issues and all PR states found no open exact duplicate for the Mach-O alias effect. No fix or post-fix restoration was tested.
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#7783
No description provided.