Skip to content

fix(security): guard os.chmod(parent) against / and top-level dirs#25831

Closed
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:fix/issue-25821-chmod-root-brick
Closed

fix(security): guard os.chmod(parent) against / and top-level dirs#25831
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:fix/issue-25821-chmod-root-brick

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

Summary

Five call sites do os.chmod(path.parent, 0o700) on a derived path without checking that the parent resolves to a safe directory. If HERMES_HOME or another path env var resolves to /, the chmod strips traversal permission from the root inode and bricks the entire host — every non-root user fails any path lookup with EACCES, taking out DNS, networking, journald, and every Docker container that drops privileges.

This was hit in production (see #25821). Root cause took 5+ hours to isolate.

Root Cause

All 5 call sites share this pattern:

path.parent.mkdir(parents=True, exist_ok=True)
try:
    os.chmod(path.parent, 0o700)
except OSError:
    pass

os.chmod("/", 0o700) raises no exception — it succeeds.

Fix

Add secure_parent_dir(path) to hermes_constants.py that refuses to chmod / or any top-level directory (depth < 2 resolved parts). Replace all 5 call sites:

  • tools/mcp_oauth.py:179_write_json (MCP OAuth tokens)
  • hermes_cli/auth.py:991_save_auth_store
  • hermes_cli/auth.py:1573_save_qwen_cli_tokens
  • hermes_cli/auth.py:2991_save_nous_shared_token
  • agent/google_oauth.py:495save_credentials

Code Intelligence

  • Analyzed: secure_parent_dir (new function), _save_auth_store, _write_json, save_credentials
  • Blast radius: LOW — the guard only blocks chmod on / and top-level dirs; all normal paths behave identically
  • Related patterns: _secure_dir() in cron/jobs.py (addresses ~/.hermes only, not the 5 credential sites)

Regression Coverage

6 new tests in tests/test_hermes_constants.py::TestSecureParentDir:

  • test_safe_path_calls_chmod — normal nested path (depth ≥ 3) calls os.chmod
  • test_root_dir_skipped/ is never chmod'd
  • test_top_level_dir_skipped/usr and similar depth-2 dirs are never chmod'd
  • test_two_component_path_skipped — paths with < 3 resolved parts are skipped
  • test_oserror_suppressed — OSError from chmod is silently caught
  • test_symlink_resolved — symlinks are resolved before depth check

Testing

  • tests/test_hermes_constants.py — 40 passed (includes all 6 new tests)
  • tests/hermes_cli/test_auth_toctou_file_modes.py — 4 passed (existing auth chmod tests)
  • tests/tools/test_mcp_oauth.py — 38 passed (existing OAuth tests)

Fixes [bug] os.chmod(path.parent, 0o700) bricks host when path.parent resolves to / #25821

@alt-glitch alt-glitch added type/security Security vulnerability or hardening P1 High — major feature broken, no workaround comp/cli CLI entry point, hermes_cli/, setup wizard area/auth Authentication, OAuth, credential pools tool/mcp MCP client and OAuth labels May 14, 2026
@liuhao1024 liuhao1024 force-pushed the fix/issue-25821-chmod-root-brick branch from cb3e7c7 to a57dda4 Compare May 14, 2026 16:50
@liuhao1024 liuhao1024 force-pushed the fix/issue-25821-chmod-root-brick branch from a57dda4 to 0857a5a Compare May 15, 2026 00:56
Five call sites do os.chmod(path.parent, 0o700) without checking that
the parent resolves to a safe directory. If HERMES_HOME or another
path env var resolves to /, the chmod strips traversal permission from
the root inode and bricks the entire host.

Add secure_parent_dir() to hermes_constants.py that refuses to chmod
/ or any top-level directory (depth < 2). Replace all 5 call sites
with this helper.

Fixes NousResearch#25821
@teknium1

Copy link
Copy Markdown
Contributor

Wait — your PR was actually salvaged. Disregard, this comment is in error.

@teknium1 teknium1 closed this May 21, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Update: this PR's substantive commit was cherry-picked verbatim into PR #29670 (now merged on main as commit 9c83e2e3... — see #29670). Your authorship is preserved in the git log. Thanks for the fix and the 5 unit tests — production-confirmed P0 closed.

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

Labels

area/auth Authentication, OAuth, credential pools comp/cli CLI entry point, hermes_cli/, setup wizard P1 High — major feature broken, no workaround tool/mcp MCP client and OAuth type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants