Skip to content

fix(subdirectory_hints): prevent loading AGENTS.md outside workspace#32103

Closed
ffr31mr wants to merge 1 commit into
NousResearch:mainfrom
ffr31mr:pr/subdirectory-hints-security
Closed

fix(subdirectory_hints): prevent loading AGENTS.md outside workspace#32103
ffr31mr wants to merge 1 commit into
NousResearch:mainfrom
ffr31mr:pr/subdirectory-hints-security

Conversation

@ffr31mr

@ffr31mr ffr31mr commented May 25, 2026

Copy link
Copy Markdown

Summary

SubdirectoryHintTracker was scanning directories outside the active working directory. This allowed files like ~/.codex/AGENTS.md or ~/.claude/CLAUDE.md to be loaded and injected into the agent context, causing cross-agent context contamination and instruction mixup.

Changes

  • Add _is_ancestor_or_same() helper for boundary checking
  • Add path boundary check in _is_valid_subdir(): only directories within the working directory tree (path.is_relative_to(working_dir)) are allowed to be scanned
  • Add exist_ok=True to mkdir() calls in new tests to prevent pytest-xdist race conditions when parallel workers share the same tmp_path parent

Tests added

  • test_outside_working_dir_rejected: sibling directories are blocked
  • test_outside_working_dir_absolute_path_rejected: paths like ~/.codex/ are blocked
  • test_inside_workspace_subdir_allowed: normal subdirectory access is unaffected
  • test_sibling_repo_not_loaded_via_ancestor_walk: ancestor walk stays within workspace

Test plan

  • pytest tests/agent/test_subdirectory_hints.py passes
  • pytest -n auto tests/agent/test_subdirectory_hints.py passes (xdist race check)

SubdirectoryHintTracker was scanning directories outside the active
working directory, allowing files like ~/.codex/AGENTS.md or
~/.claude/CLAUDE.md to be loaded and injected into the agent context.
This causes cross-agent context contamination and instruction mixup.

Add _is_ancestor_or_same() helper and a path boundary check in
_is_valid_subdir(): only directories within the working directory tree
(i.e. path.is_relative_to(working_dir)) are allowed.

Also add exist_ok=True to mkdir() calls in new tests to prevent
pytest-xdist race conditions when workers share the same tmp_path parent.

Tests added:
- test_outside_working_dir_rejected: verifies sibling dirs are blocked
- test_outside_working_dir_absolute_path_rejected: verifies ~/.codex paths blocked
- test_inside_workspace_subdir_allowed: verifies normal subdir access unaffected
- test_sibling_repo_not_loaded_via_ancestor_walk: ancestor walk stays within workspace
@alt-glitch alt-glitch added type/security Security vulnerability or hardening comp/agent Core agent loop, run_agent.py, prompt builder P1 High — major feature broken, no workaround labels May 25, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of PR #14510 (and competing PR #14795), both of which fix the same workspace boundary check in SubdirectoryHintTracker._is_valid_subdir() to prevent loading AGENTS.md/CLAUDE.md from outside the working directory. All three PRs address issue #14471.

@hclsys hclsys left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is correct and the boundary check works — I traced both candidate sources and they're safe:

  • working_dir is real-path resolved at construction (agent/subdirectory_hints.py:62).
  • Every path that reaches _is_valid_subdir is also resolved first: _add_path_candidate does p = p.resolve() (:122) before walking ancestors into the guard, and _extract_paths_from_command resolves at :124. _load_hints_for_directory only sees dirs from that already-resolved candidate set.

So a symlink inside the workspace pointing outside (./link → ~/.codex) resolves outside working_dir and correctly fails the new is_relative_to check — the contamination path the PR targets is closed. Good. The _is_ancestor_or_same fallback for the pre-3.9 is_relative_to absence is also right (not redundant — it's the except branch).

One correctness-of-comment nit worth fixing (not a logic bug): the inline comment

# path.resolve() may differ from working_dir.resolve() due to symlinks,
# but path.is_relative_to(working_dir) handles both absolute and
# symlinked paths correctly on Python 3.9+.

is misleading — Path.is_relative_to is purely lexical; it does no symlink resolution. The symlink safety here comes entirely from callers resolving the path before calling this method (:122/:124), not from is_relative_to. As written, the comment could lead a future caller to add an unresolved path and silently lose the guard. Suggest rewording to something like "callers must pass a resolved path; is_relative_to is a lexical containment check." Optionally assert/resolve at the top of _is_valid_subdir to make the method self-defending.

Otherwise LGTM.

teknium1 added a commit that referenced this pull request May 26, 2026
Pre-salvage prep for the must-have security cluster (#32103, #32155).
#32103 author commit uses dearmayo@localhost; PR opener is ffr31mr —
same pattern as the existing holynn-q localhost mapping.
@teknium1

Copy link
Copy Markdown
Contributor

Salvaged in PR #32342 → merged to main. Your workspace-boundary fix is in; ancestor walk in SubdirectoryHintTracker now gates on is_relative_to(working_dir). Authorship preserved via rebase-merge.

Thanks for catching the cross-agent contamination vector — this is a real indirect-prompt-injection sink and the fix is the right shape.

bridge25 pushed a commit to bridge25/hermes-agent that referenced this pull request May 27, 2026
Pre-salvage prep for the must-have security cluster (NousResearch#32103, NousResearch#32155).
NousResearch#32103 author commit uses dearmayo@localhost; PR opener is ffr31mr —
same pattern as the existing holynn-q localhost mapping.
mathias3 pushed a commit to mathias3/hermes-agent that referenced this pull request May 28, 2026
Pre-salvage prep for the must-have security cluster (NousResearch#32103, NousResearch#32155).
NousResearch#32103 author commit uses dearmayo@localhost; PR opener is ffr31mr —
same pattern as the existing holynn-q localhost mapping.
Bryce-huang pushed a commit to wbkunlun/hermes-agent that referenced this pull request May 29, 2026
Pre-salvage prep for the must-have security cluster (NousResearch#32103, NousResearch#32155).
NousResearch#32103 author commit uses dearmayo@localhost; PR opener is ffr31mr —
same pattern as the existing holynn-q localhost mapping.

#AI commit#
mosaiq-systems pushed a commit to mosaiq-systems/hermes-agent that referenced this pull request May 29, 2026
Pre-salvage prep for the must-have security cluster (NousResearch#32103, NousResearch#32155).
NousResearch#32103 author commit uses dearmayo@localhost; PR opener is ffr31mr —
same pattern as the existing holynn-q localhost mapping.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
Pre-salvage prep for the must-have security cluster (NousResearch#32103, NousResearch#32155).
NousResearch#32103 author commit uses dearmayo@localhost; PR opener is ffr31mr —
same pattern as the existing holynn-q localhost mapping.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P1 High — major feature broken, no workaround type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants