Skip to content

fix: resolve symlink bypass in write deny list on macOS#61

Merged
teknium1 merged 1 commit intoNousResearch:mainfrom
0xbyt4:fix/write-deny-macos-symlink
Feb 27, 2026
Merged

fix: resolve symlink bypass in write deny list on macOS#61
teknium1 merged 1 commit intoNousResearch:mainfrom
0xbyt4:fix/write-deny-macos-symlink

Conversation

@0xbyt4
Copy link
Copy Markdown
Contributor

@0xbyt4 0xbyt4 commented Feb 26, 2026

Summary

  • _is_write_denied() was bypassed on macOS for all /etc/* paths
  • On macOS, /etc is a symlink to /private/etc. The function resolved the input path with os.path.realpath() but deny list entries were stored as literal strings (/etc/shadow), so the resolved path /private/etc/shadow never matched.
  • Fix: Apply os.path.realpath() to deny list entries at module load time so both sides use resolved paths

Affected paths (were unprotected on macOS)

  • /etc/shadow, /etc/passwd, /etc/sudoers
  • /etc/sudoers.d/*, /etc/systemd/*

Test plan

  • 19 regression tests in tests/tools/test_write_deny.py — all passing on macOS
  • Verified fix with inline Python check for all 7 affected paths
On macOS, /etc is a symlink to /private/etc. The _is_write_denied()
function resolves the input path with os.path.realpath() but the deny
list entries were stored as literal strings ("/etc/shadow"). This meant
the resolved path "/private/etc/shadow" never matched, allowing writes
to sensitive system files on macOS.

Fix: Apply os.path.realpath() to deny list entries at module load time
so both sides of the comparison use resolved paths.

Adds 19 regression tests in tests/tools/test_write_deny.py.
0xbyt4 added a commit to 0xbyt4/hermes-agent that referenced this pull request Feb 26, 2026
These tests documented the macOS symlink bypass bug with
platform-conditional assertions. The fix and proper regression
tests are in PR NousResearch#61 (tests/tools/test_write_deny.py), so remove
them here to avoid ordering conflicts between the two PRs.
@teknium1 teknium1 merged commit 0909be3 into NousResearch:main Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants