[GH-ISSUE #6097] [Bug] Permission-denial tests fail under CAP_DAC_OVERRIDE #3730

Closed
opened 2026-10-31 00:43:27 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @rxmna8502 on GitHub (Oct 21, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6097

Before submitting

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

Area

Setup, packaging, or upgrade

Observed behavior

Running the source test group in a non-root Linux container with
CAP_DAC_OVERRIDE produced seven permission-related test failures.

The process ran as UID/GID 1000/1000, but mode-000 files remained readable
because the process had CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH.

Observed test result:

  • 13,082 passed
  • 7 failed
  • 9 skipped

The failures occurred in:

  • src/application/environment/environmentBundleWrite.test.ts
  • src/hopper/HopperMachOImage.test.ts
  • src/hopper/HopperTargetLease.test.ts
  • src/ida/IdaWorkspaceOwnership.test.ts
  • src/process/capture/FilesystemCoverage.test.ts

This was observed once in the described environment and reproduced by a
separate mode-000 file-read probe.

Reproduction

From source revision
f86206535f8ec71014781f9d432a84b01e183e5d:

npm ci --ignore-scripts
npm run test:fast

### Expected behavior


## Expected behavior

```markdown
Unverified expectation: permission-denial tests should not fail merely because
a non-root test process has DAC-bypass capabilities.

The fixtures should either:

1. Check whether the host actually enforces the intended permission denial
   before running; or
2. Inject deterministic `EACCES`/`EPERM` failures for unit-level error-path
   coverage.

A smaller POSIX integration test could remain guarded by an actual permission
probe.

### Impact and scope

Observed impact is limited to development/test execution: the source test group
reports seven failures in capability-enabled Linux containers.

This can produce false-negative CI or contributor results even though the
process is not UID 0.

No REA runtime, evidence-integrity, provider, process-isolation, or end-user
behavior defect was established by this reproduction. Behavior on the
repository's official CI runners is untested.

### REA version

Source revision f86206535f8ec71014781f9d432a84b01e183e5d (package version 6.7.0)

### Runtime and platform

Node.js: 24.14.1 OS: Amazon Linux 2023 Architecture: x86_64 UID/GID: 1000/1000  Relevant effective capabilities: - CAP_DAC_OVERRIDE - CAP_DAC_READ_SEARCH  Hopper/Ghidra: not applicable

### Target and artifact context

Not applicable. This concerns repository permission-denial test fixtures; no reverse-engineering target or artifact was used.

### Interface

Other

### Earliest failure stage

Unknown

### Relevant output or evidence

Smallest observed test summary:

```text
Test Files  5 failed | 784 passed (789)
Tests       7 failed | 13082 passed | 9 skipped (13098)

### Typed outcome or recovery expected


## Typed outcome or recovery expected

```markdown
Not applicable to a public REA interface.

At the test-harness level, the fixture should either skip with an explicit
unsupported-host reason or deterministically inject the intended
`EACCES`/`EPERM` result.

### Analysis or suspected cause

Source-derived reasoning: the affected tests use guards similar to:

```ts
process.platform === "win32" || process.getuid?.() === 0

### Additional context


## Additional context

```markdown
Checks performed:

- `npm run check:fast` — passed
- `npm run verify:architecture-guards` — passed across 1,727 source files
- Python tests — 132 passed
- `npm run test:fast` — seven permission-fixture failures described above

Official CI behavior and other container runtimes remain untested.

I can prepare a focused fix after maintainers indicate whether they prefer a
shared host-capability probe, deterministic filesystem fault injection, or both.
Originally created by @rxmna8502 on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6097 ### 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 Setup, packaging, or upgrade ### Observed behavior Running the source test group in a non-root Linux container with `CAP_DAC_OVERRIDE` produced seven permission-related test failures. The process ran as UID/GID 1000/1000, but mode-000 files remained readable because the process had `CAP_DAC_OVERRIDE` and `CAP_DAC_READ_SEARCH`. Observed test result: - 13,082 passed - 7 failed - 9 skipped The failures occurred in: - `src/application/environment/environmentBundleWrite.test.ts` - `src/hopper/HopperMachOImage.test.ts` - `src/hopper/HopperTargetLease.test.ts` - `src/ida/IdaWorkspaceOwnership.test.ts` - `src/process/capture/FilesystemCoverage.test.ts` This was observed once in the described environment and reproduced by a separate mode-000 file-read probe. ### Reproduction From source revision `f86206535f8ec71014781f9d432a84b01e183e5d`: ```bash npm ci --ignore-scripts npm run test:fast ### Expected behavior ## Expected behavior ```markdown Unverified expectation: permission-denial tests should not fail merely because a non-root test process has DAC-bypass capabilities. The fixtures should either: 1. Check whether the host actually enforces the intended permission denial before running; or 2. Inject deterministic `EACCES`/`EPERM` failures for unit-level error-path coverage. A smaller POSIX integration test could remain guarded by an actual permission probe. ### Impact and scope Observed impact is limited to development/test execution: the source test group reports seven failures in capability-enabled Linux containers. This can produce false-negative CI or contributor results even though the process is not UID 0. No REA runtime, evidence-integrity, provider, process-isolation, or end-user behavior defect was established by this reproduction. Behavior on the repository's official CI runners is untested. ### REA version Source revision f86206535f8ec71014781f9d432a84b01e183e5d (package version 6.7.0) ### Runtime and platform Node.js: 24.14.1 OS: Amazon Linux 2023 Architecture: x86_64 UID/GID: 1000/1000 Relevant effective capabilities: - CAP_DAC_OVERRIDE - CAP_DAC_READ_SEARCH Hopper/Ghidra: not applicable ### Target and artifact context Not applicable. This concerns repository permission-denial test fixtures; no reverse-engineering target or artifact was used. ### Interface Other ### Earliest failure stage Unknown ### Relevant output or evidence Smallest observed test summary: ```text Test Files 5 failed | 784 passed (789) Tests 7 failed | 13082 passed | 9 skipped (13098) ### Typed outcome or recovery expected ## Typed outcome or recovery expected ```markdown Not applicable to a public REA interface. At the test-harness level, the fixture should either skip with an explicit unsupported-host reason or deterministically inject the intended `EACCES`/`EPERM` result. ### Analysis or suspected cause Source-derived reasoning: the affected tests use guards similar to: ```ts process.platform === "win32" || process.getuid?.() === 0 ### Additional context ## Additional context ```markdown Checks performed: - `npm run check:fast` — passed - `npm run verify:architecture-guards` — passed across 1,727 source files - Python tests — 132 passed - `npm run test:fast` — seven permission-fixture failures described above Official CI behavior and other container runtimes remain untested. I can prepare a focused fix after maintainers indicate whether they prefer a shared host-capability probe, deterministic filesystem fault injection, or both.
bsakaguchi 2026-10-31 00:43:27 +00:00
  • closed this issue
  • added the
    bug
    triage
    labels
Author
Owner

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

I'd like to take this one.

Edit: Main fixed this in d0375a1ed instead. Test results are here.

I can reproduce the condition here without root. Inside unshare -U --keep-caps, a uid 1000 process keeps DAC-bypass caps. A mode-000 file then stays readable.

The plan is one shared test fixture. It checks once whether a mode-000 file is really unreadable. The seven tests skip on that check instead of the root-only one. Only test files change.

Thanks, rxmna8502, for the clear report and the two fix options.

🤖 Written with Claude Code (Opus 5.5), posted with my OK 🦊

<!-- gh-comment-id:6328460556 --> @CassieFox23 commented on GitHub (Oct 21, 2026): I'd like to take this one. Edit: Main fixed this in d0375a1ed instead. Test results are [here](https://github.com/morluto/rea/issues/6097#issuecomment-6388506734). I can reproduce the condition here without root. Inside `unshare -U --keep-caps`, a uid 1000 process keeps DAC-bypass caps. A mode-000 file then stays readable. The plan is one shared test fixture. It checks once whether a mode-000 file is really unreadable. The seven tests skip on that check instead of the root-only one. Only test files change. Thanks, rxmna8502, for the clear report and the two fix options. 🤖 Written with Claude Code (Opus 5.5), posted with my OK 🦊
Author
Owner

@rxmna8502 commented on GitHub (Oct 22, 2026):

Thanks for reproducing it and picking this up! The shared permission probe sounds like a focused fix. I’m interested in contributing to REA too happy to help validate the patch in the original environment when it’s ready.

<!-- gh-comment-id:6331935745 --> @rxmna8502 commented on GitHub (Oct 22, 2026): Thanks for reproducing it and picking this up! The shared permission probe sounds like a focused fix. I’m interested in contributing to REA too happy to help validate the patch in the original environment when it’s ready.
Author
Owner

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

Main's d0375a1ed fixes this, so the issue may be ready to close. Thanks, morluto, for the fix. That commit's message does not name this issue, so it stayed open.

DAC override is a Linux capability. It lets a normal user ignore file permission bits. With it on, the permission tests now skip instead of failing. I ran the five test files on main (31fc9762a) with Node 22, 24 and 26. Each run exits 0 with 66 passed, 9 skipped, 0 failed. The issue named 7 tests; #6264 added two more after it was filed.

Not run

Real root, macOS and Windows.

Test results

The five test files are named in the Commands fold. 75 tests on main now.

Run Node 22.23.1 Node 24.22.1 Node 26.12.1
Normal exit 0; 75 passed, 0 skipped exit 0; 75 passed, 0 skipped exit 0; 75 passed, 0 skipped
DAC override kept (uid 1000, all capabilities) exit 0; 66 passed, 9 skipped, 0 failed exit 0; 66 passed, 9 skipped, 0 failed exit 0; 66 passed, 9 skipped, 0 failed
Only CAP_DAC_READ_SEARCH kept exit 0; 69 passed, 6 skipped, 0 failed exit 0; 69 passed, 6 skipped, 0 failed exit 0; 69 passed, 6 skipped, 0 failed
Before the fix (63370e930, parent of d0375a1ed), DAC override kept not run exit 1; 9 failed, 64 passed not run

The 9 skips under DAC override are the 7 tests from the issue plus the two #6264 tests. Each skip prints its reason. One example: Host bypasses POSIX file-read denial; real permission coverage is unverified. With only CAP_DAC_READ_SEARCH kept, the 6 read and search tests skip and the 3 write tests run and pass. The before-the-fix run used the node_modules from the main install, so treat it as a control only.

Commands
git archive upstream/main | tar -x -C /tmp/rt-6097main
npm --prefix /tmp/rt-6097main ci --ignore-scripts
HUSKY=0 npm --prefix /tmp/rt-6097main run prepare

# normal
npm --prefix /tmp/rt-6097main run test:focused -- src/application/environment/environmentBundleWrite.test.ts src/hopper/HopperMachOImage.test.ts src/hopper/HopperTargetLease.test.ts src/ida/IdaWorkspaceOwnership.test.ts src/process/capture/FilesystemCoverage.test.ts

# DAC override kept: the same command inside
unshare -U --map-user=1000 --map-group=1000 --keep-caps sh -c '...'

# only CAP_DAC_READ_SEARCH kept: the same command inside
unshare -U --map-user=1000 --map-group=1000 --keep-caps setpriv --inh-caps=-all,+dac_read_search --ambient-caps=-all,+dac_read_search --bounding-set=-all,+dac_read_search -- sh -c '...'

Each wrapper run printed CapEff first: 000001ffffffffff for the override runs, 0000000000000004 for the read-search run. Node 22, 24 and 26 were picked by putting that Node first on PATH (24 at ~/.local/opt/node24/bin, 26 at ~/.local/opt/node26/bin, 22 at /usr/bin).

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: 22.23.1, 24.22.1, 26.12.1
  • Head / base: 31fc9762a (main, tested) / 63370e930 (parent of d0375a1ed, control run)
  • Other: Vitest 5.0.3. Main has since moved to 425776174; it changes none of the five test files, the fixture or docs/testing.md (checked by diff, not re-run). Node 22.23.1 is below the engines floor on main (^22.23.4); npm run did not object.

🤖 Written and tested with Claude Code (Sonnet 5.5, verified by Opus 5.5), posted with my OK 🦊

<!-- gh-comment-id:6388506734 --> @CassieFox23 commented on GitHub (Oct 24, 2026): Main's d0375a1ed fixes this, so the issue may be ready to close. Thanks, morluto, for the fix. That commit's message does not name this issue, so it stayed open. DAC override is a Linux capability. It lets a normal user ignore file permission bits. With it on, the permission tests now skip instead of failing. I ran the five test files on main (31fc9762a) with Node 22, 24 and 26. Each run exits 0 with 66 passed, 9 skipped, 0 failed. The issue named 7 tests; #6264 added two more after it was filed. ### Not run Real root, macOS and Windows. <details> <summary>Test results</summary> The five test files are named in the Commands fold. 75 tests on main now. | Run | Node 22.23.1 | Node 24.22.1 | Node 26.12.1 | |---|---|---|---| | Normal | exit 0; 75 passed, 0 skipped | exit 0; 75 passed, 0 skipped | exit 0; 75 passed, 0 skipped | | DAC override kept (uid 1000, all capabilities) | exit 0; 66 passed, 9 skipped, 0 failed | exit 0; 66 passed, 9 skipped, 0 failed | exit 0; 66 passed, 9 skipped, 0 failed | | Only CAP_DAC_READ_SEARCH kept | exit 0; 69 passed, 6 skipped, 0 failed | exit 0; 69 passed, 6 skipped, 0 failed | exit 0; 69 passed, 6 skipped, 0 failed | | Before the fix (63370e930, parent of d0375a1ed), DAC override kept | not run | exit 1; 9 failed, 64 passed | not run | The 9 skips under DAC override are the 7 tests from the issue plus the two #6264 tests. Each skip prints its reason. One example: `Host bypasses POSIX file-read denial; real permission coverage is unverified`. With only CAP_DAC_READ_SEARCH kept, the 6 read and search tests skip and the 3 write tests run and pass. The before-the-fix run used the node_modules from the main install, so treat it as a control only. </details> <details> <summary>Commands</summary> ``` git archive upstream/main | tar -x -C /tmp/rt-6097main npm --prefix /tmp/rt-6097main ci --ignore-scripts HUSKY=0 npm --prefix /tmp/rt-6097main run prepare # normal npm --prefix /tmp/rt-6097main run test:focused -- src/application/environment/environmentBundleWrite.test.ts src/hopper/HopperMachOImage.test.ts src/hopper/HopperTargetLease.test.ts src/ida/IdaWorkspaceOwnership.test.ts src/process/capture/FilesystemCoverage.test.ts # DAC override kept: the same command inside unshare -U --map-user=1000 --map-group=1000 --keep-caps sh -c '...' # only CAP_DAC_READ_SEARCH kept: the same command inside unshare -U --map-user=1000 --map-group=1000 --keep-caps setpriv --inh-caps=-all,+dac_read_search --ambient-caps=-all,+dac_read_search --bounding-set=-all,+dac_read_search -- sh -c '...' ``` Each wrapper run printed CapEff first: 000001ffffffffff for the override runs, 0000000000000004 for the read-search run. Node 22, 24 and 26 were picked by putting that Node first on PATH (24 at ~/.local/opt/node24/bin, 26 at ~/.local/opt/node26/bin, 22 at /usr/bin). </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: 22.23.1, 24.22.1, 26.12.1 - Head / base: 31fc9762a (main, tested) / 63370e930 (parent of d0375a1ed, control run) - Other: Vitest 5.0.3. Main has since moved to 425776174; it changes none of the five test files, the fixture or docs/testing.md (checked by diff, not re-run). Node 22.23.1 is below the engines floor on main (^22.23.4); `npm run` did not object. </details> 🤖 Written and tested with Claude Code (Sonnet 5.5, verified by Opus 5.5), posted with my OK 🦊
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#3730
No description provided.