[PR #6178] [CLOSED] test(fixtures): skip permission-denial tests when DAC-bypass caps keep files readable #7186

Closed
opened 2026-11-01 21:45:59 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/6178
Author: @CassieFox23
Created: 10/21/2026
Status: ❌ Closed

Base: main ← Head: fix/6097-dac-override-tests


📝 Commits (2)

  • 4a84ebd test: skip permission-denial tests when DAC-bypass caps keep files readable
  • 91e71a4 test(fixtures): split read and write permission-denial probes

📊 Changes

6 files changed (+103 additions, -7 deletions)

View changed files

📝 src/application/environment/environmentBundleWrite.test.ts (+3 -2)
📝 src/hopper/HopperMachOImage.test.ts (+2 -1)
📝 src/hopper/HopperTargetLease.test.ts (+2 -1)
📝 src/ida/IdaWorkspaceOwnership.test.ts (+2 -1)
📝 src/process/capture/FilesystemCoverage.test.ts (+3 -2)
➕ tests/fixtures/permissionDenial.ts (+91 -0)

📄 Description

TL;DR: Seven tests failed when a process can skip file permission checks. They only skipped for root. Now they skip whenever the system does not actually refuse access. Only test files changed.

Summary

Fixes #6097.

Seven permission-denial tests skipped only when the user id was 0. A normal user can keep CAP_DAC_OVERRIDE. That process can still read a mode-000 file. So those tests failed there. A new shared fixture checks once whether the system really refuses access, and the tests skip when it does not.

Problem and expected behavior

On a Linux process with user id 1000 and CAP_DAC_OVERRIDE kept, a file set to mode 000 still reads fine. The tests expect a permission error, so they fail. A test that needs a permission denial should skip when the denial cannot happen. Root was already covered by the user id check. This case was not.

Change and scope

  • Adds tests/fixtures/permissionDenial.ts. It runs once when imported and exports two flags: readDenialEnforced and writeDenialEnforced.
  • The read probe makes a temp folder. It reads a mode-000 file. It lists a mode-000 folder and a mode-333 folder. The flag is true only when all three are refused.
  • The write probe adds a file to a mode-500 folder. The flag is true only when that is refused. CAP_DAC_READ_SEARCH does not skip this check, so the flag stays true there.
  • Both flags are false on Windows. The temp folder is removed afterwards.
  • In five test files, process.getuid?.() === 0 became a flag check in seven skip checks. Four tests need a read denial and use !readDenialEnforced. They are in HopperMachOImage, HopperTargetLease and FilesystemCoverage (two). Three tests need a write denial and use !writeDenialEnforced. They are the two in environmentBundleWrite and the one in IdaWorkspaceOwnership. The Windows conditions did not change.
  • Not included: 38 other test files use the same root-only check (getuid?.() === 0). Moving them to the new fixture could be a follow-up. It is not changed here.

Follow the contribution scope.
Unresolved scope, authority, side effects, or required verification block
integration. Link the required maintainer scope decision for new providers or
capabilities involving target execution, injection, mutation, or protection
recovery; ordinary fixes within existing boundaries may state "not applicable".
Draft PRs may leave the scope decision pending and the readiness and merge checklists
unchecked. Maintainers can help resolve open questions.

Contract and boundary impact

  • Semantic owner and earliest changed stage:
    Test fixtures and five test files. No production code.
  • Contribution scope fit and linked maintainer decision:
    Not applicable. This is an ordinary test fix inside existing boundaries.
  • CLI and MCP/tool-catalog contract:
    None.
  • Provider, bridge, target-format, or platform compatibility:
    None. The probe returns false on Windows. I did not run Windows or macOS.
  • Evidence, artifact, provenance, or reconstruction contract:
    None.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable):
    Not applicable. The probe makes and removes its own temp folder.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs and skills/; edit authored instructions in .agents/skills/reverse-engineer-anything/ and never commit build outputs):
    None.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • 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

Evidence is executed. The failure was reproduced on main, then the fix was run under the same condition.

The condition: unshare -U --map-user="$(id -u)" --map-group="$(id -g)" --keep-caps. Inside it the process had user id 1000 and CapEff: 000001ffffffffff.

Run of the 5 files Node Main This branch
Normal 22.23.1, 24.18.0, 26.11.1 64 passed, 0 skipped 64 passed, 0 skipped
Under the condition 22.23.1, 24.18.0, 26.11.1 7 failed, 57 passed 0 failed, 57 passed, 7 skipped
Read-search only (user id 1000, CapEff: 0000000000000004) 22.23.1, 24.18.0, 26.11.1 not run 0 failed, 60 passed, 4 skipped

The counts were the same on all three Node versions.

The read-search-only run keeps CAP_DAC_READ_SEARCH. It drops every other capability. In that state a mode-000 file and folder could be read, and a write into a mode-500 folder was refused (EACCES). The 4 read-denial tests skipped. The 3 write-denial tests ran and passed. On the previous head 4a84ebdf2, in the same state on Node 22.23.1 only, all 7 tests skipped (57 passed, 7 skipped). That is the case that review found.

  • Tests added or updated:
    No new tests. Seven existing skip checks in five files now use the shared fixture: four use the read flag and three use the write flag.
  • Base reproduction or other evidence:
    The seven failures above, on main d7ca93cc2, under the condition.
  • User-visible CLI/MCP output (if applicable):
    Not applicable.
  • Remaining proof gaps:
    Not run as real root, on macOS, or on Windows.

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

  • npm run test:focused -- <the 5 files> on Node 22.23.1, 24.18.0 and 26.11.1: 64 passed on each, 0 skipped
  • Same command under the unshare condition on main: 7 failed, 57 passed on each Node
  • Same command under the unshare condition on this branch: 0 failed, 57 passed, 7 skipped on each Node
  • Same command with only CAP_DAC_READ_SEARCH kept, on this branch: 0 failed, 60 passed, 4 skipped on each Node
  • Same command with only CAP_DAC_READ_SEARCH kept, on the old head 4a84ebdf2 (Node 22 only): 57 passed, 7 skipped
  • npm run typecheck on Node 22.23.1: passed
  • npm run lint on Node 22.23.1: 0 errors, 2119 warnings, none in the fixture
  • npm run format:check on Node 22.23.1: passed
  • Full test suite, macOS, Windows, real root: not run

Compatibility, safety, and release

  • Breaking changes or migration steps:
    None.
  • Real Hopper/Ghidra, browser, or OS coverage:
    None needed. The tests use temp files only.
  • Package or release metadata impact:
    None.
  • Security, privacy, process, or containment review:
    None. The probe only touches its own temp folder.

Author readiness

Human owner: @CassieFox23

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

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.

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

Commands
unshare -U --map-user="$(id -u)" --map-group="$(id -g)" --keep-caps sh -c 'npm 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'
unshare -U --map-user="$(id -u)" --map-group="$(id -g)" --keep-caps setpriv --inh-caps=-all,+dac_read_search --ambient-caps=-all,+dac_read_search --bounding-set=-all,+dac_read_search sh -c 'npm run test:focused -- <the same 5 files>'
npm run test:focused -- <the same 5 files>
npm run typecheck
npm run lint
npm run format:check
Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.11
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 31 GiB usable (free -g)
  • Node: 22.23.1, 24.18.0, 26.11.1
  • Head / base: 91e71a422 / d7ca93cc2

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


🔄 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/6178 **Author:** [@CassieFox23](https://github.com/CassieFox23) **Created:** 10/21/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/6097-dac-override-tests` --- ### 📝 Commits (2) - [`4a84ebd`](https://github.com/morluto/rea/commit/4a84ebdf280d0c016e1cf6f5bcaf325eb0766d9a) test: skip permission-denial tests when DAC-bypass caps keep files readable - [`91e71a4`](https://github.com/morluto/rea/commit/91e71a42212239141afb69da7cf864aafecc538b) test(fixtures): split read and write permission-denial probes ### 📊 Changes **6 files changed** (+103 additions, -7 deletions) <details> <summary>View changed files</summary> 📝 `src/application/environment/environmentBundleWrite.test.ts` (+3 -2) 📝 `src/hopper/HopperMachOImage.test.ts` (+2 -1) 📝 `src/hopper/HopperTargetLease.test.ts` (+2 -1) 📝 `src/ida/IdaWorkspaceOwnership.test.ts` (+2 -1) 📝 `src/process/capture/FilesystemCoverage.test.ts` (+3 -2) ➕ `tests/fixtures/permissionDenial.ts` (+91 -0) </details> ### 📄 Description **TL;DR:** Seven tests failed when a process can skip file permission checks. They only skipped for root. Now they skip whenever the system does not actually refuse access. Only test files changed. ## Summary Fixes #6097. Seven permission-denial tests skipped only when the user id was 0. A normal user can keep CAP_DAC_OVERRIDE. That process can still read a mode-000 file. So those tests failed there. A new shared fixture checks once whether the system really refuses access, and the tests skip when it does not. ## Problem and expected behavior On a Linux process with user id 1000 and CAP_DAC_OVERRIDE kept, a file set to mode 000 still reads fine. The tests expect a permission error, so they fail. A test that needs a permission denial should skip when the denial cannot happen. Root was already covered by the user id check. This case was not. ## Change and scope - Adds `tests/fixtures/permissionDenial.ts`. It runs once when imported and exports two flags: `readDenialEnforced` and `writeDenialEnforced`. - The read probe makes a temp folder. It reads a mode-000 file. It lists a mode-000 folder and a mode-333 folder. The flag is true only when all three are refused. - The write probe adds a file to a mode-500 folder. The flag is true only when that is refused. CAP_DAC_READ_SEARCH does not skip this check, so the flag stays true there. - Both flags are false on Windows. The temp folder is removed afterwards. - In five test files, `process.getuid?.() === 0` became a flag check in seven skip checks. Four tests need a read denial and use `!readDenialEnforced`. They are in `HopperMachOImage`, `HopperTargetLease` and `FilesystemCoverage` (two). Three tests need a write denial and use `!writeDenialEnforced`. They are the two in `environmentBundleWrite` and the one in `IdaWorkspaceOwnership`. The Windows conditions did not change. - Not included: 38 other test files use the same root-only check (`getuid?.() === 0`). Moving them to the new fixture could be a follow-up. It is not changed here. Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). Unresolved scope, authority, side effects, or required verification block integration. Link the required maintainer scope decision for new providers or capabilities involving target execution, injection, mutation, or protection recovery; ordinary fixes within existing boundaries may state "not applicable". Draft PRs may leave the scope decision pending and the readiness and merge checklists unchecked. Maintainers can help resolve open questions. ## Contract and boundary impact - Semantic owner and earliest changed stage: Test fixtures and five test files. No production code. - Contribution scope fit and linked maintainer decision: Not applicable. This is an ordinary test fix inside existing boundaries. - CLI and MCP/tool-catalog contract: None. - Provider, bridge, target-format, or platform compatibility: None. The probe returns false on Windows. I did not run Windows or macOS. - Evidence, artifact, provenance, or reconstruction contract: None. - Target selection, authority, upstream behavior, and cleanup details (if applicable): Not applicable. The probe makes and removes its own temp folder. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs): None. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [ ] 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 Evidence is executed. The failure was reproduced on main, then the fix was run under the same condition. The condition: `unshare -U --map-user="$(id -u)" --map-group="$(id -g)" --keep-caps`. Inside it the process had user id 1000 and `CapEff: 000001ffffffffff`. | Run of the 5 files | Node | Main | This branch | |---|---|---|---| | Normal | 22.23.1, 24.18.0, 26.11.1 | 64 passed, 0 skipped | 64 passed, 0 skipped | | Under the condition | 22.23.1, 24.18.0, 26.11.1 | 7 failed, 57 passed | 0 failed, 57 passed, 7 skipped | | Read-search only (user id 1000, `CapEff: 0000000000000004`) | 22.23.1, 24.18.0, 26.11.1 | not run | 0 failed, 60 passed, 4 skipped | The counts were the same on all three Node versions. The read-search-only run keeps `CAP_DAC_READ_SEARCH`. It drops every other capability. In that state a mode-000 file and folder could be read, and a write into a mode-500 folder was refused (EACCES). The 4 read-denial tests skipped. The 3 write-denial tests ran and passed. On the previous head 4a84ebdf2, in the same state on Node 22.23.1 only, all 7 tests skipped (57 passed, 7 skipped). That is the case that review found. - Tests added or updated: No new tests. Seven existing skip checks in five files now use the shared fixture: four use the read flag and three use the write flag. - Base reproduction or other evidence: The seven failures above, on main d7ca93cc2, under the condition. - User-visible CLI/MCP output (if applicable): Not applicable. - Remaining proof gaps: Not run as real root, on macOS, or on Windows. 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 - `npm run test:focused -- <the 5 files>` on Node 22.23.1, 24.18.0 and 26.11.1: 64 passed on each, 0 skipped - Same command under the `unshare` condition on main: 7 failed, 57 passed on each Node - Same command under the `unshare` condition on this branch: 0 failed, 57 passed, 7 skipped on each Node - Same command with only CAP_DAC_READ_SEARCH kept, on this branch: 0 failed, 60 passed, 4 skipped on each Node - Same command with only `CAP_DAC_READ_SEARCH` kept, on the old head 4a84ebdf2 (Node 22 only): 57 passed, 7 skipped - `npm run typecheck` on Node 22.23.1: passed - `npm run lint` on Node 22.23.1: 0 errors, 2119 warnings, none in the fixture - `npm run format:check` on Node 22.23.1: passed - Full test suite, macOS, Windows, real root: not run ## Compatibility, safety, and release - Breaking changes or migration steps: None. <!-- For ! / BREAKING CHANGE: show a previously valid call/configuration, its new behavior and migration, and why compatibility/deprecation cannot preserve it. Every release increments minor, including breaking changes; keep breaking markers and migration notes so callers can assess compatibility. See docs/releasing.md. --> - Real Hopper/Ghidra, browser, or OS coverage: None needed. The tests use temp files only. - Package or release metadata impact: None. - Security, privacy, process, or containment review: None. The probe only touches its own temp folder. ## Author readiness <!-- The human owner completes these declarations before requesting review. The PR author is the owner by default. A different owner must explicitly accept ownership on this PR. AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA. Draft PRs may leave them unchecked. These declarations record initial readiness when opening the PR for review; later pushes do not require a new human sign-off. Keep the description and validation evidence current as the work changes. See CONTRIBUTING.md#pull-request-ownership-and-readiness. --> Human owner: @CassieFox23 - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge <!-- Draft PRs may leave pending items unchecked. Resolve material scope and authority questions before integration. --> - [ ] 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. Thanks, rxmna8502, for the clear report and the two fix options. <details> <summary>Commands</summary> ``` unshare -U --map-user="$(id -u)" --map-group="$(id -g)" --keep-caps sh -c 'npm 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' unshare -U --map-user="$(id -u)" --map-group="$(id -g)" --keep-caps setpriv --inh-caps=-all,+dac_read_search --ambient-caps=-all,+dac_read_search --bounding-set=-all,+dac_read_search sh -c 'npm run test:focused -- <the same 5 files>' npm run test:focused -- <the same 5 files> npm run typecheck npm run lint npm run format:check ``` </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: 31 GiB usable (`free -g`) - Node: 22.23.1, 24.18.0, 26.11.1 - Head / base: 91e71a422 / d7ca93cc2 </details> 🤖 Written and tested with Claude Code (Sonnet 5.5, verified by Opus 5.5), posted with my OK 🦊 --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:45:59 +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#7186
No description provided.