Skip to content

fix(file): harden read_file device alias blocking - #34466

Closed
egilewski wants to merge 2 commits into
NousResearch:mainfrom
egilewski:codex/read-file-device-alias-guard
Closed

fix(file): harden read_file device alias blocking#34466
egilewski wants to merge 2 commits into
NousResearch:mainfrom
egilewski:codex/read-file-device-alias-guard

Conversation

@egilewski

@egilewski egilewski commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • normalize expanded read paths before checking the blocked-device helper
  • inspect symlink hops before falling back to final realpath
  • add regression coverage for /dev aliases and a symlink to a blocked alias

Classification

This is a security-hardening residual bypass fix for the read_file device blocklist, not a new sandbox boundary.

Upstream already has a resolved-path pass for ordinary workspace symlinks to blocked devices. This PR covers the remaining alias class: paths or symlink targets that are dangerous before final terminal-specific resolution, such as /dev/../dev/stdin.

Why

read_file is supposed to reject blocking or infinite device streams before file I/O. Comparing only the expanded spelling lets model-supplied aliases depend on final realpath behavior:

  • /dev/../dev/zero and /dev/./urandom should match the blocklist as concrete path aliases
  • /dev/stdin-style aliases can disappear after realpath follows them through /proc/self/fd/0 to a tty path
  • a user symlink can point at /dev/../dev/stdin, exposing the blocked target before final resolution

The fix normalizes paths inside the concrete helper and checks each symlink hop, while preserving the existing /proc fd and pseudo-file protections.

Tests

  • /home/mac/hermes-agent/.venv/bin/python -m pytest tests/tools/test_file_read_guards.py -q -> 41 passed
  • /home/mac/hermes-agent/.venv/bin/python -m pytest tests/tools/test_file_tools.py tests/tools/test_file_tools_live.py tests/tools/test_file_operations.py tests/tools/test_file_operations_edge_cases.py tests/agent/test_file_safety_credentials.py -q -> 214 passed
  • /home/mac/hermes-agent/.venv/bin/python -m py_compile tools/file_tools.py tests/tools/test_file_read_guards.py
  • git diff --check upstream/main..HEAD

Fixes #10141
Fixes #29158

@egilewski egilewski changed the title fix(file): block device path aliases in read_file fix(file): harden read_file device alias blocking May 29, 2026
@egilewski
egilewski force-pushed the codex/read-file-device-alias-guard branch from bc443b7 to 55ed231 Compare May 29, 2026 08:02
@liuhao1024

Copy link
Copy Markdown
Contributor

Verified: the normpath + symlink-hop approach correctly closes the alias bypass.

os.path.normpath() on the literal path: /dev/../dev/zero/dev/zero (matched by _BLOCKED_DEVICE_PATHS). Without normpath, the .. component evaded the string equality check. This is the right fix — normpath is cheap and deterministic.

Symlink hop walk with cycle protection: the seen set + 20-hop limit prevents both infinite loops (symlink cycles) and pathological chains. Each hop resolves one level via os.readlink() and checks against _BLOCKED_DEVICE_PATHS before continuing. This catches the case where a workspace symlink points to /dev/stdin via an intermediate alias.

Final realpath check still included: the code retains os.path.normpath(os.path.realpath(normalized)) as a fallback after the hop walk. This catches any edge case the hop walk misses (e.g., bind mounts, procfs aliases). Correct defense-in-depth.

One minor note: the hop walk calls _is_blocked_device_path(target) which itself calls os.path.normpath(os.path.expanduser(target)). Since target is already normpath'd from the hop resolution, the inner normpath is a no-op — harmless but redundant. Not worth changing.

@alt-glitch alt-glitch added type/security Security vulnerability or hardening P1 High — major feature broken, no workaround tool/file File tools (read, write, patch, search) labels May 29, 2026
@egilewski

Copy link
Copy Markdown
Contributor Author

My Codex neglected to mention that the initial commit was found to be conflicting after #10133 was merged, and after rebasing reduced in scope to amend what was left behind and close the issues.

Security-hardening fix for the read_file device guard, not a new sandbox
boundary. The guard already rejects direct device paths and upstream now
has a resolved-path pass for workspace symlinks to blocked devices, but
its concrete-path helper still compared the expanded path before
normalization. That leaves residual alias cases where the dangerous path
is visible before final terminal-specific resolution, for example:

  1. /dev/../dev/zero and /dev/./urandom should match the blocked-device
     list as concrete paths, not only after final realpath;
  2. /dev/stdin-style aliases can disappear once realpath follows them
     to /proc/self/fd/0 and then to a tty path;
  3. a user symlink to /dev/../dev/stdin exposes the dangerous
     intermediate target before final resolution, but not necessarily
     after it.

Normalize expanded paths before matching and inspect each symlink hop
before falling back to realpath. This preserves the existing /proc fd and
/proc pseudo-file guards while enforcing the intended security invariant:
model-supplied read paths must not reach blocking or infinite device
streams through spelling, normalization, or symlink-hop tricks.

Classification: security hardening / residual bypass fix for the
read_file device blocklist. This is defensive code at the file-tool
boundary, but it fixes a concrete denial-of-service class tracked as
security in NousResearch#10141 and NousResearch#29158.

Tests:
  - normalized /dev/../dev/zero and /dev/./urandom aliases
  - symlink to /dev/../dev/stdin blocked before realpath
  - existing symlink-to-device and regular-symlink guards still pass

Fixes NousResearch#10141
Fixes NousResearch#29158
The read_file device guard now walks symlink hops before the file operation
layer, but that hop walk still interpreted relative paths against the Python
process cwd. In sessions where TERMINAL_CWD points at the task workspace, a
relative workspace symlink to a blocked alias such as /dev/../dev/stdin could
therefore miss the intermediate device target before later task-cwd resolution.

Anchor relative device checks to the task base before symlink-hop inspection so
the pre-I/O guard sees the same workspace path that read_file would otherwise
read. Absolute device paths and the existing final realpath fallback remain
unchanged.

Refs NousResearch#10141
Refs NousResearch#29158
@egilewski
egilewski force-pushed the codex/read-file-device-alias-guard branch from 130b769 to acee75a Compare June 21, 2026 17:18
@egilewski

Copy link
Copy Markdown
Contributor Author

Rebased and force-pushed the original branch onto current origin/main (65a477f12).

The branch now keeps the original hardening commit as its own signed commit and adds the TERMINAL_CWD relative-symlink fix as a separate signed follow-up:

  • 8a73c72a8 fix(file): harden read_file device alias blocking
  • acee75a84 fix(file): anchor device symlink guard to task cwd

The follow-up fixes the remaining case where a relative workspace symlink to /dev/../dev/stdin could be interpreted against the Python process cwd during the symlink-hop walk before read_file resolved it against the task cwd.

Validation on the local branch before push: focused device-guard pytest passed, the mocked bypass probe no longer reached the read sink, py_compile passed, git diff --check passed, and CodeRabbit reported 0 findings.

Signed: GPT-5.5 in Codex

@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #50221. Your commit was cherry-picked onto current main with your authorship preserved in git log (commit 9078b4b, author: Eugeniusz Gilewski). The per-symlink-hop check closes the genuine residual bypass (a symlink through /dev/stdin that realpath would resolve past to a tty) plus the normpath path-alias class. Thanks for the hardening work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P1 High — major feature broken, no workaround tool/file File tools (read, write, patch, search) type/security Security vulnerability or hardening

Projects

None yet

4 participants