[GH-ISSUE #5458] [Bug] environment-export reports read access denied for an unwritable output directory #3316

Closed
opened 2026-10-31 00:39:06 +00:00 by bsakaguchi · 3 comments
Owner

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

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

environment-export correctly refuses an existing empty output directory with mode 0500, but reports a read-access failure even though that directory is readable. Its message, recovery advice and filesystem-read boundary describe the wrong access mode. Changing the same directory to mode 0700 makes the same export succeed.

An independent verifier executed the CLI reproduction and controls below on the stated source revision. This report was prepared with AI assistance; the submission agent checked upstream history and duplicates without claiming a new runtime reproduction.

Reproduction

From the baseline checkout with dependencies and built runtime available, use the pinned Node.js 24.18.0 as a non-root POSIX user on a filesystem that enforces owner permission bits. The fixture generates synthetic capture data; the CLI commands construct a valid authenticated capture/manifest pair. Each REA process uses isolated home, XDG and temporary directories.

node_bin="$(command -v node)"
case_dir="$(mktemp -d)"
mkdir -p "$case_dir/home" "$case_dir/config" "$case_dir/data" "$case_dir/cache" "$case_dir/state" "$case_dir/temp"
export HOME="$case_dir/home"
export XDG_CONFIG_HOME="$case_dir/config"
export XDG_DATA_HOME="$case_dir/data"
export XDG_CACHE_HOME="$case_dir/cache"
export XDG_STATE_HOME="$case_dir/state"
export TMPDIR="$case_dir/temp"
export NODE_DISABLE_COMPILE_CACHE=1

"$node_bin" --input-type=module -e 'import { environmentManifestHar } from "./tests/fixtures/historicalHar.ts"; process.stdout.write(JSON.stringify(environmentManifestHar()));' > "$case_dir/session.har"
"$node_bin" scripts/rea.mjs inspect-web-network-capture "$case_dir/session.har" har --format json > "$case_dir/capture.json"
"$node_bin" --input-type=module -e 'import fs from "node:fs"; const root = process.argv[1]; const evidence = JSON.parse(fs.readFileSync(root + "/capture.json", "utf8")); fs.writeFileSync(root + "/view.json", JSON.stringify({source:{kind:"inline",evidence},view:{kind:"environment-manifest",template_rules:["after-uid-literal","opaque-token","sibling-variance"]}}));' "$case_dir"
"$node_bin" scripts/rea.mjs inspect-analysis-view "$case_dir/view.json" --format json > "$case_dir/manifest.json"

mkdir "$case_dir/empty-output"
chmod 500 "$case_dir/empty-output"
"$node_bin" scripts/rea.mjs environment-export "$case_dir/manifest.json" "$case_dir/empty-output" --evidence "$case_dir/capture.json" --json
printf 'exit=%s\n' "$?"

The denied export exited 1 and left the output directory empty. The verifier then executed this control with the same inputs:

chmod 700 "$case_dir/empty-output"
"$node_bin" scripts/rea.mjs environment-export "$case_dir/manifest.json" "$case_dir/empty-output" --evidence "$case_dir/capture.json" --json

The control exited 0 with status: exported, 19 tools and seven files. Every listed file existed with its reported byte count. One failing-output case and its writable-output control are documented; repeated-run frequency was not measured.

Expected behavior

Identify an output-write permission denial and advise making the destination writable or selecting a writable destination. Continue to refuse writes denied by host permissions. docs/cli.md:398-406 defines export as writing into an absent or empty bundle directory; docs/cli.md:473-481 describes structured failures and host write failures. Source-derived: src/domain/analysisErrorCore.ts:188-203 defines the current access-denied error specifically as a filesystem read denial.

Impact and scope

The diagnostic sends callers toward checking read access that already exists. The tested write was correctly refused, with no files written, data loss or security-boundary bypass observed. P3 wording/metadata defect. Adding owner write permission restored the same export in the executed control.

The verifier also ran an unreadable-manifest control, which correctly returned a read diagnostic naming the input and created no output directory. A non-empty-output control returned invalid_request with "Output directory is not empty" and preserved its existing file. Later-file failures, EPERM, Windows permissions and ACL-specific cases were not tested.

REA version

6.7.0; source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8. This was a source build, not a separate installation of the published package. No later changes to the cited files were found when checking upstream main at 052956af3416fa01d93837e54fdec3ad2739f6d1.

Runtime and platform

Node.js 24.18.0; macOS 27.0.1; Darwin arm64; non-root user. No Hopper or Ghidra provider is involved.

Target and artifact context

Synthetic HAR from tests/fixtures/historicalHar.ts (environmentManifestHar), projected through the public CLI into authenticated capture Evidence and an environment manifest. The destination was an existing empty directory with mode 0500.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

The failing environment-export command returned these fields with exit 1. The generated local output path is omitted.

{
  "error": "Application workflow failed",
  "code": "access_denied",
  "category": "unavailable",
  "message": "Host filesystem permissions denied read access to the selected path.",
  "retryable": false,
  "remediation": {
    "action": "Check the current process's read access to the selected path. Retry with a readable local file."
  },
  "details": {
    "operation": "environment-export",
    "system_code": "EACCES",
    "boundary": "filesystem-read"
  }
}

The independent verifier observed the output directory empty before and after the denial. The writable-output, input-read and occupied-output controls above distinguish the failed access mode.

Typed outcome or recovery expected

An accurate output-write diagnostic with applicable recovery advice. No particular replacement error code is required. Preserve the input-read diagnostic and occupied-directory refusal.

Analysis or suspected cause

Source-derived reasoning at the tested revision: src/application/environment/environmentExport.ts:289-295 maps non-AnalysisError permission failures from bundle writing through accessDeniedError, defined at lines 93-120. src/domain/analysisErrorPresentation.ts:123-127,248-249 supplies read-access advice and wording; src/domain/analysisErrorProjection.ts:297-302 supplies filesystem-read. Existing AnalysisError instances pass through unchanged, so this report does not generalize to every permission failure in the workflow.

Additional context

No known-good version was established. No source changes or mocks were used for the verifier's reproduction. All target data was synthetic; the reproduction made no network requests.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5458 ### 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 `environment-export` correctly refuses an existing empty output directory with mode 0500, but reports a read-access failure even though that directory is readable. Its message, recovery advice and `filesystem-read` boundary describe the wrong access mode. Changing the same directory to mode 0700 makes the same export succeed. An independent verifier executed the CLI reproduction and controls below on the stated source revision. This report was prepared with AI assistance; the submission agent checked upstream history and duplicates without claiming a new runtime reproduction. ### Reproduction From the baseline checkout with dependencies and built runtime available, use the pinned Node.js 24.18.0 as a non-root POSIX user on a filesystem that enforces owner permission bits. The fixture generates synthetic capture data; the CLI commands construct a valid authenticated capture/manifest pair. Each REA process uses isolated home, XDG and temporary directories. ```sh node_bin="$(command -v node)" case_dir="$(mktemp -d)" mkdir -p "$case_dir/home" "$case_dir/config" "$case_dir/data" "$case_dir/cache" "$case_dir/state" "$case_dir/temp" export HOME="$case_dir/home" export XDG_CONFIG_HOME="$case_dir/config" export XDG_DATA_HOME="$case_dir/data" export XDG_CACHE_HOME="$case_dir/cache" export XDG_STATE_HOME="$case_dir/state" export TMPDIR="$case_dir/temp" export NODE_DISABLE_COMPILE_CACHE=1 "$node_bin" --input-type=module -e 'import { environmentManifestHar } from "./tests/fixtures/historicalHar.ts"; process.stdout.write(JSON.stringify(environmentManifestHar()));' > "$case_dir/session.har" "$node_bin" scripts/rea.mjs inspect-web-network-capture "$case_dir/session.har" har --format json > "$case_dir/capture.json" "$node_bin" --input-type=module -e 'import fs from "node:fs"; const root = process.argv[1]; const evidence = JSON.parse(fs.readFileSync(root + "/capture.json", "utf8")); fs.writeFileSync(root + "/view.json", JSON.stringify({source:{kind:"inline",evidence},view:{kind:"environment-manifest",template_rules:["after-uid-literal","opaque-token","sibling-variance"]}}));' "$case_dir" "$node_bin" scripts/rea.mjs inspect-analysis-view "$case_dir/view.json" --format json > "$case_dir/manifest.json" mkdir "$case_dir/empty-output" chmod 500 "$case_dir/empty-output" "$node_bin" scripts/rea.mjs environment-export "$case_dir/manifest.json" "$case_dir/empty-output" --evidence "$case_dir/capture.json" --json printf 'exit=%s\n' "$?" ``` The denied export exited 1 and left the output directory empty. The verifier then executed this control with the same inputs: ```sh chmod 700 "$case_dir/empty-output" "$node_bin" scripts/rea.mjs environment-export "$case_dir/manifest.json" "$case_dir/empty-output" --evidence "$case_dir/capture.json" --json ``` The control exited 0 with `status: exported`, 19 tools and seven files. Every listed file existed with its reported byte count. One failing-output case and its writable-output control are documented; repeated-run frequency was not measured. ### Expected behavior Identify an output-write permission denial and advise making the destination writable or selecting a writable destination. Continue to refuse writes denied by host permissions. `docs/cli.md:398-406` defines export as writing into an absent or empty bundle directory; `docs/cli.md:473-481` describes structured failures and host write failures. Source-derived: `src/domain/analysisErrorCore.ts:188-203` defines the current access-denied error specifically as a filesystem read denial. ### Impact and scope The diagnostic sends callers toward checking read access that already exists. The tested write was correctly refused, with no files written, data loss or security-boundary bypass observed. P3 wording/metadata defect. Adding owner write permission restored the same export in the executed control. The verifier also ran an unreadable-manifest control, which correctly returned a read diagnostic naming the input and created no output directory. A non-empty-output control returned `invalid_request` with "Output directory is not empty" and preserved its existing file. Later-file failures, EPERM, Windows permissions and ACL-specific cases were not tested. ### REA version 6.7.0; source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`. This was a source build, not a separate installation of the published package. No later changes to the cited files were found when checking upstream `main` at `052956af3416fa01d93837e54fdec3ad2739f6d1`. ### Runtime and platform Node.js 24.18.0; macOS 27.0.1; Darwin arm64; non-root user. No Hopper or Ghidra provider is involved. ### Target and artifact context Synthetic HAR from `tests/fixtures/historicalHar.ts` (`environmentManifestHar`), projected through the public CLI into authenticated capture Evidence and an environment manifest. The destination was an existing empty directory with mode 0500. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence The failing `environment-export` command returned these fields with exit 1. The generated local output path is omitted. ```json { "error": "Application workflow failed", "code": "access_denied", "category": "unavailable", "message": "Host filesystem permissions denied read access to the selected path.", "retryable": false, "remediation": { "action": "Check the current process's read access to the selected path. Retry with a readable local file." }, "details": { "operation": "environment-export", "system_code": "EACCES", "boundary": "filesystem-read" } } ``` The independent verifier observed the output directory empty before and after the denial. The writable-output, input-read and occupied-output controls above distinguish the failed access mode. ### Typed outcome or recovery expected An accurate output-write diagnostic with applicable recovery advice. No particular replacement error code is required. Preserve the input-read diagnostic and occupied-directory refusal. ### Analysis or suspected cause Source-derived reasoning at the tested revision: `src/application/environment/environmentExport.ts:289-295` maps non-`AnalysisError` permission failures from bundle writing through `accessDeniedError`, defined at lines 93-120. `src/domain/analysisErrorPresentation.ts:123-127,248-249` supplies read-access advice and wording; `src/domain/analysisErrorProjection.ts:297-302` supplies `filesystem-read`. Existing `AnalysisError` instances pass through unchanged, so this report does not generalize to every permission failure in the workflow. ### Additional context No known-good version was established. No source changes or mocks were used for the verifier's reproduction. All target data was synthetic; the reproduction made no network requests.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with sibling environment CLI reports in Cluster AL: #5455 ([Bug] environment-serve suggests changing ports for host lookup failures) and #5457. Both address error classification and misleading remediation messages in environment-export and environment-serve.

Consolidated under Cluster AL (Environment CLI Command Error Projection & Remediation) in tracker #4330.

<!-- gh-comment-id:6291449447 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with sibling environment CLI reports in **Cluster AL**: #5455 (`[Bug] environment-serve suggests changing ports for host lookup failures`) and #5457. Both address error classification and misleading remediation messages in `environment-export` and `environment-serve`. Consolidated under **Cluster AL (Environment CLI Command Error Projection & Remediation)** in tracker #4330.
Author
Owner

@CassieFox23 commented on GitHub (Oct 20, 2026):

TL;DR: This still happens on main on Linux. An output directory you cannot write to gets a read-access error. Making the directory writable fixes the export.

Checked on main b3aadaf47 (Node 24.18.0). environment-export into an empty directory with mode 0500 exits 1 with access_denied. The message says Host filesystem permissions denied read access to the selected path. The advice is to check read access. The details say EACCES and boundary: filesystem-read. After chmod 700 on the same directory, the export exits 0.

Not run

  • The issue's HAR capture chain. I used the repo fixture from #5457 and copied the export command from it.
  • A non-empty 0500 directory, a read-only parent, macOS.

Thanks, coygeek, for the clear report.

Commands
chmod 500 <case_dir>/empty-output
node scripts/rea.mjs environment-export <case_dir>/manifest.json <case_dir>/empty-output --evidence <case_dir>/capture.json --json
chmod 700 <case_dir>/empty-output
node scripts/rea.mjs environment-export <case_dir>/manifest.json <case_dir>/empty-output --evidence <case_dir>/capture.json --json

First run, exit 1, stderr 0 bytes:

{"error":"Application workflow failed","code":"access_denied","category":"unavailable","message":"Host filesystem permissions denied read access to the selected path.","retryable":false,"remediation":{"action":"Check the current process's read access to the selected path. Retry with a readable local file."},"details":{"operation":"environment-export","path":"<case_dir>/empty-output","system_code":"EACCES","boundary":"filesystem-read"}}

Second run, exit 0: "status":"exported", "tool_count":19. Isolated HOME, XDG and TMPDIR; run once per case.

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.11
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 24.18.0
  • Head: b3aadaf47

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6298170649 --> @CassieFox23 commented on GitHub (Oct 20, 2026): **TL;DR:** This still happens on main on Linux. An output directory you cannot write to gets a read-access error. Making the directory writable fixes the export. Checked on main b3aadaf47 (Node 24.18.0). `environment-export` into an empty directory with mode 0500 exits 1 with `access_denied`. The message says `Host filesystem permissions denied read access to the selected path.` The advice is to check read access. The details say `EACCES` and `boundary: filesystem-read`. After `chmod 700` on the same directory, the export exits 0. ### Not run - The issue's HAR capture chain. I used the repo fixture from #5457 and copied the export command from it. - A non-empty 0500 directory, a read-only parent, macOS. Thanks, coygeek, for the clear report. <details> <summary>Commands</summary> ```sh chmod 500 <case_dir>/empty-output node scripts/rea.mjs environment-export <case_dir>/manifest.json <case_dir>/empty-output --evidence <case_dir>/capture.json --json chmod 700 <case_dir>/empty-output node scripts/rea.mjs environment-export <case_dir>/manifest.json <case_dir>/empty-output --evidence <case_dir>/capture.json --json ``` First run, exit 1, stderr 0 bytes: ```json {"error":"Application workflow failed","code":"access_denied","category":"unavailable","message":"Host filesystem permissions denied read access to the selected path.","retryable":false,"remediation":{"action":"Check the current process's read access to the selected path. Retry with a readable local file."},"details":{"operation":"environment-export","path":"<case_dir>/empty-output","system_code":"EACCES","boundary":"filesystem-read"}} ``` Second run, exit 0: `"status":"exported"`, `"tool_count":19`. Isolated HOME, XDG and TMPDIR; run once per case. </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.11 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 24.18.0 - Head: b3aadaf47 </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

Output export permission failures now default to write access and return an output error with accurate writing message, writable-destination recovery and filesystem-write/path/system_code diagnostics. Input reads preserve read-specific classification. The physical permission regression uses an existing empty mode-0500 destination, checks EACCES on README.md with filesystem-write and no created entries; adjacent tests cover nested write denial.

Implementation: environmentExport.ts, environmentExport.ts.

The relevant regression checks in environmentBundleWrite.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380778154 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. Output export permission failures now default to write access and return an output error with accurate writing message, writable-destination recovery and filesystem-write/path/system_code diagnostics. Input reads preserve read-specific classification. The physical permission regression uses an existing empty mode-0500 destination, checks EACCES on README.md with filesystem-write and no created entries; adjacent tests cover nested write denial. Implementation: [environmentExport.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/environment/environmentExport.ts#L162), [environmentExport.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/environment/environmentExport.ts#L374). The relevant regression checks in [environmentBundleWrite.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/environment/environmentBundleWrite.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#3316
No description provided.