Skip to content

fix(file-safety): block read_file on HERMES_HOME credential stores (#17656)#17659

Closed
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/file-safety-deny-auth-json-17656
Closed

fix(file-safety): block read_file on HERMES_HOME credential stores (#17656)#17659
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/file-safety-deny-auth-json-17656

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

Summary

  • Extend agent/file_safety.py::get_read_block_error to deny reads of ${HERMES_HOME}/auth.json, ${HERMES_HOME}/auth.lock, and ${HERMES_HOME}/.anthropic_oauth.json.
  • Adds 8 unit tests covering the deny list, the existing skills/.hub regression, path traversal, symlink indirection, and the negative case (arbitrary HERMES_HOME files remain readable).

The bug

read_file is sandboxed to HERMES_HOME, but inside that scope get_read_block_error only denies skills/.hub/. That leaves the credential-pool files fully readable by the agent:

>>> from agent.file_safety import get_read_block_error
>>> get_read_block_error(\"/opt/data/auth.json\")
>>> # Returns None — read is allowed.

auth.json materializes at \${HERMES_HOME}/auth.json mode 0600 with the active key as plaintext in credential_pool.<provider>[].access_token. Mode 0600 prevents other Unix users from reading the file, but the agent runs as the file's owner — read_file is unaffected. A prompt-injection attack reaching read_file can exfiltrate active provider credentials in plaintext.

The fix

Extend get_read_block_error with a second deny set covering the three credential paths identified in #17656:

  • \${HERMES_HOME}/auth.json — provider OAuth state + plaintext API keys
  • \${HERMES_HOME}/auth.lock — auth-store lock companion
  • \${HERMES_HOME}/.anthropic_oauth.json — Anthropic PKCE tokens

The check uses the same Path.resolve() pattern as the existing skills/.hub block, so symlink shims and path traversal are caught too. The denial message points at the boundary so the agent (and humans reading the trace) understand why the read failed.

The agent doesn't need to read these directly — auxiliary_client and credential_pool consume them through process env / OAuth flows that bypass read_file.

Test plan

  • Focused tests pass: tests/agent/test_file_safety_credentials.py (8 tests).
  • Regression guard: on clean origin/main, the 5 tests that assert credential-file blocking fail; with the patch they pass. The 3 negative/regression tests (skills/.hub still blocked, arbitrary files stay readable, subdirectory auth.json not over-blocked) pass on both branches.
  • Adjacent suite: tests/tools/test_file_read_guards.py and tests/agent/test_copilot_acp_client.py show the same 7 baseline failures on clean origin/main and on this branch (HERMES_WRITE_SAFE_ROOT-related write-path tests, unrelated to this change).

Related

Copilot AI review requested due to automatic review settings April 29, 2026 23:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Extends Hermes’ get_read_block_error read guard to deny read_file access to credential-store files located under HERMES_HOME, reducing the risk of prompt-injection credential exfiltration.

Changes:

  • Block reads of ${HERMES_HOME}/auth.json, ${HERMES_HOME}/auth.lock, and ${HERMES_HOME}/.anthropic_oauth.json in agent/file_safety.py.
  • Add a new focused pytest module with credential-store blocking, traversal, symlink, regression, and negative-case coverage.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
agent/file_safety.py Adds a credential-store deny set to get_read_block_error alongside the existing skills/.hub block.
tests/agent/test_file_safety_credentials.py Introduces unit tests verifying blocking behavior for credential-store paths and related edge cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread agent/file_safety.py
Comment on lines +112 to +123
# Credential stores under HERMES_HOME hold plaintext provider keys
# and OAuth tokens. The agent never needs to read these directly —
# auxiliary_client / credential_pool consume them through process
# env / OAuth flows that bypass read_file. Block read access so a
# prompt-injection reaching read_file can't exfiltrate them.
blocked_credential_files = {
hermes_home / "auth.json",
hermes_home / "auth.lock",
hermes_home / ".anthropic_oauth.json",
}
if resolved in blocked_credential_files:
return (
assert "credential store" in err
assert "auth.json" in err


Comment thread agent/file_safety.py
Comment on lines +112 to +117
# Credential stores under HERMES_HOME hold plaintext provider keys
# and OAuth tokens. The agent never needs to read these directly —
# auxiliary_client / credential_pool consume them through process
# env / OAuth flows that bypass read_file. Block read access so a
# prompt-injection reaching read_file can't exfiltrate them.
blocked_credential_files = {
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P0 Critical — data loss, security, crash loop comp/agent Core agent loop, run_agent.py, prompt builder tool/file File tools (read, write, patch, search) area/auth Authentication, OAuth, credential pools labels Apr 29, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related to #17656 (the issue this fixes) and #16851 (broader read_file sensitive-path deny list). This PR specifically targets HERMES_HOME credential stores.

1 similar comment
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related to #17656 (the issue this fixes) and #16851 (broader read_file sensitive-path deny list). This PR specifically targets HERMES_HOME credential stores.

@briandevans

Copy link
Copy Markdown
Contributor Author

@copilot All three findings addressed in commit 38e473f9d:

  1. Relative-path bypassread_file_tool resolves against TERMINAL_CWD while get_read_block_error resolves against the Python process cwd. Fixed by passing the already-resolved str(_resolved) at the file_tools call site (tools/file_tools.py:478).
  2. Test coverage gap — added test_read_file_tool_blocks_relative_path_under_terminal_cwd which sets TERMINAL_CWD to a fake HERMES_HOME, chdirs the process elsewhere, and confirms read_file("auth.json") is denied. Verified the test fails against the original call (returns None) and passes against the fix.
  3. Docstringget_read_block_error now documents both blocked categories (skills/.hub cache + HERMES_HOME credential stores) and the caller contract that relative paths must be pre-resolved.

briandevans and others added 2 commits May 22, 2026 00:12
…ousResearch#17656)

`get_read_block_error` previously only denied reads inside
`${HERMES_HOME}/skills/.hub`, which left `auth.json` (provider OAuth
state + plaintext API keys) and `.anthropic_oauth.json` (Anthropic PKCE
tokens) directly readable by the agent. A prompt-injection reaching
`read_file` could exfiltrate active provider credentials in plaintext.

Mode-0600 file permissions only protect against *other Unix users* —
the agent runs as the file's owner, so `read_file` is unaffected.

Extend the existing deny list with the three credential paths
identified in NousResearch#17656 (`auth.json`, `auth.lock`, `.anthropic_oauth.json`).
The check uses the same `Path.resolve()` pattern as `skills/.hub`, so
symlink/path-traversal indirection is caught too. The agent doesn't
need to read these directly — `auxiliary_client` and `credential_pool`
consume them through process env / OAuth flows that bypass `read_file`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
read_file_tool resolves relative paths against TERMINAL_CWD (or the
task's live terminal cwd), but the prior call passed the original
unresolved string to get_read_block_error. That function's own
resolve() is anchored at the Python process cwd, so when a task's
TERMINAL_CWD pointed at HERMES_HOME and the agent issued read_file
on the relative path "auth.json", the credential-store denylist was
never reached and the file was read normally.

Pass the already-resolved absolute path string at the file_tools call
site, document the contract on get_read_block_error, and add a
read_file_tool-level regression test that pins the relative-path
case under TERMINAL_CWD == HERMES_HOME.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briandevans briandevans force-pushed the fix/file-safety-deny-auth-json-17656 branch from 18258a6 to dc46b4f Compare May 22, 2026 07:12
teknium1 added a commit that referenced this pull request May 23, 2026
…ts, root

Extends @briandevans's PR #17659 from {auth.json, auth.lock,
.anthropic_oauth.json} to also cover:

  - HERMES_HOME/.env                       (provider API keys)
  - HERMES_HOME/webhook_subscriptions.json (per-route HMAC secrets)
  - HERMES_HOME/mcp-tokens/                (OAuth token directory; dir
                                            + everything inside)

…AND iterates over both _hermes_home_path() AND _hermes_root_path()
so profile-mode runs (HERMES_HOME = <root>/profiles/<name>) also block
<root>/{auth.json, .env, mcp-tokens/, ...}. Same widening shape as the
write-deny side already does (#15981, #14157).

Explicitly NOT a security boundary. Per the personal-assistant trust
model, the terminal tool runs as the same OS user and can `cat
auth.json` directly. This read-deny exists as defense-in-depth:

  - Models that respect tool denials empirically tend to stop rather
    than reach for the shell.
  - The denial surfaces an audit trail when something tries to read
    credentials — easier to spot in logs than a generic `cat`.

Docstring + error message both flag this as defense-in-depth so future
contributors don't mistake it for a real security boundary and don't
re-decline reports that propose the same fix shape.

Absorbs the .env and mcp-tokens/ coverage from @tomqiaozc's parallel
PR #8055 (closed-as-duplicate, credited).

Co-authored-by: Tom Qiao <zqiao@microsoft.com>
@teknium1

Copy link
Copy Markdown
Contributor

Thanks @briandevans — clean implementation and a great catch on the TERMINAL_CWD bypass (that subtle resolved-path mismatch was easy to miss and you nailed it). Merged via salvage PR #30721 (rebase-merge, both your commits preserved on main as 056e00a7 and 567ea612).

Two things added on top during salvage:

  1. Widening to .env, webhook_subscriptions.json, mcp-tokens/, and the root view — absorbed the broader path coverage from @tomqiaozc's parallel PR fix(security): block file_tools from reading auth.json, mcp-tokens, .env #8055 (he filed against the same issue with a wider set of paths). Now iterates over both _hermes_home_path() and _hermes_root_path() so profile-mode (HERMES_HOME = <root>/profiles/<name>) doesn't leave the global root credential stores readable.

  2. Explicit "defense-in-depth, not a security boundary" framing. Per the personal-assistant trust model, the terminal tool runs as the same OS user and can cat ~/.hermes/auth.json directly — any read_file deny is bypassable in principle. The docstring and error message both call this out so future contributors / reviewers don't mistake it for a real boundary. We're shipping anyway because models that respect tool denials tend to stop on the error, the deny surfaces in audit logs, and prompt-injection vectors that only know read_file genuinely can't read the file.

17 tests total (9 yours + 8 covering the widening), 135 file-safety tests green overall.

Closing this PR — fix is on main: #30721

Issue #17656 closes automatically.

@teknium1 teknium1 closed this May 23, 2026
hfm77788 added a commit to hfm77788/hermes-agent that referenced this pull request May 23, 2026
* feat(ci): 4-way matrix slicing with LPT duration-balanced distribution

run_tests_parallel.py:
  - --slice I/N flag (also HERMES_TEST_SLICE env var) runs only the
    I-th slice of N, distributing files across slices by cached
    duration using LPT (Longest Processing Time first) greedy
    algorithm so each slice gets roughly equal wall time
  - Duration cache (test_durations.json): maps relative file paths to
    last-observed subprocess wall time. _save_durations merges with
    existing cache so entries from other slices are preserved.
  - Per-file subprocess timing in progress output + end-of-run
    distribution summary (percentiles, top-10 slowest, <1s/<2s counts)
  - Unknown files default to 2.0s estimate (~P50), spread evenly by LPT

.github/workflows/tests.yml:
  - Matrix strategy: slice [1, 2, 3, 4] with fail-fast: false
  - Each slice restores duration cache from main (stable key, no SHA),
    runs its portion, uploads per-slice durations as artifacts
  - save-durations job (main only, if: always()) downloads all 4
    artifacts, merges into single cache entry for future PRs
  - Timeout reduced from 60min to 30min per slice (~1/4 the work)

Cache design:
  - Stable key (test-durations) not keyed by commit SHA — durations
    are about files, not commits, and SHA-keyed caches miss on every
    new commit and on PR merge commits
  - actions/cache scoping: main's cache is visible to all PRs targeting
    main; feature branches without a cache still work (default 2.0s)
  - No dotfile prefix (upload-artifact v7 skips hidden files)

* test: 4-way slice benchmark (with cache save)

* fix(test): deflake two intermittent CI failures

- test_browser_secret_exfil: mock _run_browser_command instead of
  launching real Chrome (secret check is pre-launch, browser is
  irrelevant to the assertion)
- test_web_server: add time.sleep(0.05) after pub.send_text() to
  yield the event loop before receive_text(). TestClient's sync mode
  can race the broadcast handler otherwise, hanging the test.

* fix: clean push triggers

* feat(ci): use 6-way slicing based on benchmark results

Benchmarked 4/5/6/7/8 slices with LPT duration-balanced distribution:
- 4 slices: 4.8m wall, 135s spread
- 5 slices: 3.4m wall, 46s spread
- 6 slices: 3.3m wall, 26s spread ← optimal
- 7 slices: 3.9m wall, 109s spread
- 8 slices: 3.7m wall, 96s spread

6 slices is the sweet spot: lowest wall time, tightest spread.
7+ gets slower due to per-slice startup overhead dominating.

Also removes benchmark branch markers from save-durations condition.

* refactor(web): dashboard typography & contrast pass

Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.

Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.

Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.

Squashes 19 commits from PR NousResearch#28832.

Co-authored-by: Hermes <noreply@nousresearch.com>

* fix(plugins): widen _sanitize_plugin_name for category-namespaced names

Follow-up to PR NousResearch#28832 — the dashboard plugin routes now accept slashed
names like `observability/langfuse` and `image_gen/openai`, but
`_sanitize_plugin_name` still rejected forward slash and so dashboard
update + remove on those plugins fell through to '404 not found' even
though they exist on disk.

Adds an opt-in `allow_subdir=True` flag that:
- Permits internal forward slashes (category-namespaced plugin keys
  emitted by `_discover_all_plugins`).
- Strips leading and trailing slashes.
- Still rejects `..` and backslash, and still asserts the resolved
  target lives inside `plugins_dir`.

Opted in at the two read-paths that operate on installed plugins:
`_require_installed_plugin` (CLI update/remove) and
`_user_installed_plugin_dir` (dashboard update/remove). The install
path keeps the default (`allow_subdir=False`) because freshly-cloned
plugins always land top-level under `~/.hermes/plugins/<name>/`.

Adds 6 targeted unit tests covering the new flag's allow/reject matrix.

* fix(skills,pairing): path traversal guard in uninstall, lock list_pending, hash file paths

- skills_hub: validate that uninstall_skill's install_path resolves
  inside SKILLS_DIR before calling shutil.rmtree, preventing recursive
  deletion of arbitrary directories via poisoned lock.json entries
- skills_hub: include file paths (not just contents) in
  bundle_content_hash so swapping filenames between files changes the
  hash, strengthening update-detection integrity
- pairing: wrap list_pending() in self._lock so _cleanup_expired() file
  writes don't race with concurrent generate_code()/approve_code() calls

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(skills): make content_hash filename-sensitive too (symmetric with bundle_content_hash)

PR NousResearch#6656 added rel_path + \x00 prefixing to ``bundle_content_hash`` so a
filename swap between two files in a bundle changes the digest. But it
only patched the in-memory side — ``content_hash`` in ``tools/skills_guard.py``
(the on-disk equivalent) still hashed file contents only.

These two functions need to stay symmetric: ``check_for_skill_updates``
compares the disk hash of an installed skill against the bundle hash
of the upstream copy. With the asymmetric fix, every clean install
showed as drifted because the digests no longer matched
(2 existing tests in ``test_skills_hub.py`` started failing as soon as
the contributor's change landed).

Apply the same ``rel_path + \x00 + content`` shape to the disk-side
function. Both functions now produce the same digest for the same skill
content laid out two ways. Documented the symmetry invariant in the
docstring so a future change to either function knows to touch both.

Also adds tests/tools/test_pr_6656_regressions.py with 10 regression
tests covering all three fixes salvaged in PR NousResearch#6656:
  - uninstall_skill path traversal (4 cases: parent segments, absolute
    paths, symlink escape, legitimate skill)
  - bundle_content_hash filename swap detection (4 cases: in-memory
    swap, identity, disk-side swap, bundle↔disk symmetry)
  - list_pending lock contract (2 cases: source-grep contract, smoke)

Also fixes AUTHOR_MAP entry for @aaronlab — their commit email
(1115117931@qq.com) maps to "aaronagent" which isn't a real GitHub
login, so changelog @mentions would 404.

* infographic: PR NousResearch#6656 skill hub safety audit salvage

* fix(file-safety): block read_file on HERMES_HOME credential stores (NousResearch#17656)

`get_read_block_error` previously only denied reads inside
`${HERMES_HOME}/skills/.hub`, which left `auth.json` (provider OAuth
state + plaintext API keys) and `.anthropic_oauth.json` (Anthropic PKCE
tokens) directly readable by the agent. A prompt-injection reaching
`read_file` could exfiltrate active provider credentials in plaintext.

Mode-0600 file permissions only protect against *other Unix users* —
the agent runs as the file's owner, so `read_file` is unaffected.

Extend the existing deny list with the three credential paths
identified in NousResearch#17656 (`auth.json`, `auth.lock`, `.anthropic_oauth.json`).
The check uses the same `Path.resolve()` pattern as `skills/.hub`, so
symlink/path-traversal indirection is caught too. The agent doesn't
need to read these directly — `auxiliary_client` and `credential_pool`
consume them through process env / OAuth flows that bypass `read_file`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(file-safety): block auth.json read via TERMINAL_CWD relative path

read_file_tool resolves relative paths against TERMINAL_CWD (or the
task's live terminal cwd), but the prior call passed the original
unresolved string to get_read_block_error. That function's own
resolve() is anchored at the Python process cwd, so when a task's
TERMINAL_CWD pointed at HERMES_HOME and the agent issued read_file
on the relative path "auth.json", the credential-store denylist was
never reached and the file was read normally.

Pass the already-resolved absolute path string at the file_tools call
site, document the contract on get_read_block_error, and add a
read_file_tool-level regression test that pins the relative-path
case under TERMINAL_CWD == HERMES_HOME.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(file-safety): widen read-deny to .env, mcp-tokens/, webhook secrets, root

Extends @briandevans's PR NousResearch#17659 from {auth.json, auth.lock,
.anthropic_oauth.json} to also cover:

  - HERMES_HOME/.env                       (provider API keys)
  - HERMES_HOME/webhook_subscriptions.json (per-route HMAC secrets)
  - HERMES_HOME/mcp-tokens/                (OAuth token directory; dir
                                            + everything inside)

…AND iterates over both _hermes_home_path() AND _hermes_root_path()
so profile-mode runs (HERMES_HOME = <root>/profiles/<name>) also block
<root>/{auth.json, .env, mcp-tokens/, ...}. Same widening shape as the
write-deny side already does (NousResearch#15981, NousResearch#14157).

Explicitly NOT a security boundary. Per the personal-assistant trust
model, the terminal tool runs as the same OS user and can `cat
auth.json` directly. This read-deny exists as defense-in-depth:

  - Models that respect tool denials empirically tend to stop rather
    than reach for the shell.
  - The denial surfaces an audit trail when something tries to read
    credentials — easier to spot in logs than a generic `cat`.

Docstring + error message both flag this as defense-in-depth so future
contributors don't mistake it for a real security boundary and don't
re-decline reports that propose the same fix shape.

Absorbs the .env and mcp-tokens/ coverage from @tomqiaozc's parallel
PR NousResearch#8055 (closed-as-duplicate, credited).

Co-authored-by: Tom Qiao <zqiao@microsoft.com>

* infographic: PR NousResearch#17659 read-deny credentials salvage

* feat(inbox): add Hermes Inbox API surface E.1

---------

Co-authored-by: ethernet <arilotter@gmail.com>
Co-authored-by: Austin Pickett <pickett.austin@gmail.com>
Co-authored-by: Hermes <noreply@nousresearch.com>
Co-authored-by: teknium1 <127238744+teknium1@users.noreply.github.com>
Co-authored-by: aaronagent <1115117931@qq.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: briandevans <252620095+briandevans@users.noreply.github.com>
Co-authored-by: Tom Qiao <zqiao@microsoft.com>
Co-authored-by: Ubuntu <ubuntu@localhost.localdomain>
Gpapas pushed a commit to Gpapas/hermes-agent that referenced this pull request May 23, 2026
…ts, root

Extends @briandevans's PR NousResearch#17659 from {auth.json, auth.lock,
.anthropic_oauth.json} to also cover:

  - HERMES_HOME/.env                       (provider API keys)
  - HERMES_HOME/webhook_subscriptions.json (per-route HMAC secrets)
  - HERMES_HOME/mcp-tokens/                (OAuth token directory; dir
                                            + everything inside)

…AND iterates over both _hermes_home_path() AND _hermes_root_path()
so profile-mode runs (HERMES_HOME = <root>/profiles/<name>) also block
<root>/{auth.json, .env, mcp-tokens/, ...}. Same widening shape as the
write-deny side already does (NousResearch#15981, NousResearch#14157).

Explicitly NOT a security boundary. Per the personal-assistant trust
model, the terminal tool runs as the same OS user and can `cat
auth.json` directly. This read-deny exists as defense-in-depth:

  - Models that respect tool denials empirically tend to stop rather
    than reach for the shell.
  - The denial surfaces an audit trail when something tries to read
    credentials — easier to spot in logs than a generic `cat`.

Docstring + error message both flag this as defense-in-depth so future
contributors don't mistake it for a real security boundary and don't
re-decline reports that propose the same fix shape.

Absorbs the .env and mcp-tokens/ coverage from @tomqiaozc's parallel
PR NousResearch#8055 (closed-as-duplicate, credited).

Co-authored-by: Tom Qiao <zqiao@microsoft.com>
Gpapas pushed a commit to Gpapas/hermes-agent that referenced this pull request May 23, 2026
Mucky010 pushed a commit to Mucky010/hermes-agent that referenced this pull request May 24, 2026
…ts, root

Extends @briandevans's PR NousResearch#17659 from {auth.json, auth.lock,
.anthropic_oauth.json} to also cover:

  - HERMES_HOME/.env                       (provider API keys)
  - HERMES_HOME/webhook_subscriptions.json (per-route HMAC secrets)
  - HERMES_HOME/mcp-tokens/                (OAuth token directory; dir
                                            + everything inside)

…AND iterates over both _hermes_home_path() AND _hermes_root_path()
so profile-mode runs (HERMES_HOME = <root>/profiles/<name>) also block
<root>/{auth.json, .env, mcp-tokens/, ...}. Same widening shape as the
write-deny side already does (NousResearch#15981, NousResearch#14157).

Explicitly NOT a security boundary. Per the personal-assistant trust
model, the terminal tool runs as the same OS user and can `cat
auth.json` directly. This read-deny exists as defense-in-depth:

  - Models that respect tool denials empirically tend to stop rather
    than reach for the shell.
  - The denial surfaces an audit trail when something tries to read
    credentials — easier to spot in logs than a generic `cat`.

Docstring + error message both flag this as defense-in-depth so future
contributors don't mistake it for a real security boundary and don't
re-decline reports that propose the same fix shape.

Absorbs the .env and mcp-tokens/ coverage from @tomqiaozc's parallel
PR NousResearch#8055 (closed-as-duplicate, credited).

Co-authored-by: Tom Qiao <zqiao@microsoft.com>
Mucky010 pushed a commit to Mucky010/hermes-agent that referenced this pull request May 24, 2026
exosyphon pushed a commit to exosyphon/hermes-agent that referenced this pull request May 24, 2026
…ts, root

Extends @briandevans's PR NousResearch#17659 from {auth.json, auth.lock,
.anthropic_oauth.json} to also cover:

  - HERMES_HOME/.env                       (provider API keys)
  - HERMES_HOME/webhook_subscriptions.json (per-route HMAC secrets)
  - HERMES_HOME/mcp-tokens/                (OAuth token directory; dir
                                            + everything inside)

…AND iterates over both _hermes_home_path() AND _hermes_root_path()
so profile-mode runs (HERMES_HOME = <root>/profiles/<name>) also block
<root>/{auth.json, .env, mcp-tokens/, ...}. Same widening shape as the
write-deny side already does (NousResearch#15981, NousResearch#14157).

Explicitly NOT a security boundary. Per the personal-assistant trust
model, the terminal tool runs as the same OS user and can `cat
auth.json` directly. This read-deny exists as defense-in-depth:

  - Models that respect tool denials empirically tend to stop rather
    than reach for the shell.
  - The denial surfaces an audit trail when something tries to read
    credentials — easier to spot in logs than a generic `cat`.

Docstring + error message both flag this as defense-in-depth so future
contributors don't mistake it for a real security boundary and don't
re-decline reports that propose the same fix shape.

Absorbs the .env and mcp-tokens/ coverage from @tomqiaozc's parallel
PR NousResearch#8055 (closed-as-duplicate, credited).

Co-authored-by: Tom Qiao <zqiao@microsoft.com>
exosyphon pushed a commit to exosyphon/hermes-agent that referenced this pull request May 24, 2026
mathias3 pushed a commit to mathias3/hermes-agent that referenced this pull request May 28, 2026
…ts, root

Extends @briandevans's PR NousResearch#17659 from {auth.json, auth.lock,
.anthropic_oauth.json} to also cover:

  - HERMES_HOME/.env                       (provider API keys)
  - HERMES_HOME/webhook_subscriptions.json (per-route HMAC secrets)
  - HERMES_HOME/mcp-tokens/                (OAuth token directory; dir
                                            + everything inside)

…AND iterates over both _hermes_home_path() AND _hermes_root_path()
so profile-mode runs (HERMES_HOME = <root>/profiles/<name>) also block
<root>/{auth.json, .env, mcp-tokens/, ...}. Same widening shape as the
write-deny side already does (NousResearch#15981, NousResearch#14157).

Explicitly NOT a security boundary. Per the personal-assistant trust
model, the terminal tool runs as the same OS user and can `cat
auth.json` directly. This read-deny exists as defense-in-depth:

  - Models that respect tool denials empirically tend to stop rather
    than reach for the shell.
  - The denial surfaces an audit trail when something tries to read
    credentials — easier to spot in logs than a generic `cat`.

Docstring + error message both flag this as defense-in-depth so future
contributors don't mistake it for a real security boundary and don't
re-decline reports that propose the same fix shape.

Absorbs the .env and mcp-tokens/ coverage from @tomqiaozc's parallel
PR NousResearch#8055 (closed-as-duplicate, credited).

Co-authored-by: Tom Qiao <zqiao@microsoft.com>
mathias3 pushed a commit to mathias3/hermes-agent that referenced this pull request May 28, 2026
Bryce-huang pushed a commit to wbkunlun/hermes-agent that referenced this pull request May 29, 2026
…ts, root

Extends @briandevans's PR NousResearch#17659 from {auth.json, auth.lock,
.anthropic_oauth.json} to also cover:

  - HERMES_HOME/.env                       (provider API keys)
  - HERMES_HOME/webhook_subscriptions.json (per-route HMAC secrets)
  - HERMES_HOME/mcp-tokens/                (OAuth token directory; dir
                                            + everything inside)

…AND iterates over both _hermes_home_path() AND _hermes_root_path()
so profile-mode runs (HERMES_HOME = <root>/profiles/<name>) also block
<root>/{auth.json, .env, mcp-tokens/, ...}. Same widening shape as the
write-deny side already does (NousResearch#15981, NousResearch#14157).

Explicitly NOT a security boundary. Per the personal-assistant trust
model, the terminal tool runs as the same OS user and can `cat
auth.json` directly. This read-deny exists as defense-in-depth:

  - Models that respect tool denials empirically tend to stop rather
    than reach for the shell.
  - The denial surfaces an audit trail when something tries to read
    credentials — easier to spot in logs than a generic `cat`.

Docstring + error message both flag this as defense-in-depth so future
contributors don't mistake it for a real security boundary and don't
re-decline reports that propose the same fix shape.

Absorbs the .env and mcp-tokens/ coverage from @tomqiaozc's parallel
PR NousResearch#8055 (closed-as-duplicate, credited).

Co-authored-by: Tom Qiao <zqiao@microsoft.com>

#AI commit#
Bryce-huang pushed a commit to wbkunlun/hermes-agent that referenced this pull request May 29, 2026
mosaiq-systems pushed a commit to mosaiq-systems/hermes-agent that referenced this pull request May 29, 2026
…ts, root

Extends @briandevans's PR NousResearch#17659 from {auth.json, auth.lock,
.anthropic_oauth.json} to also cover:

  - HERMES_HOME/.env                       (provider API keys)
  - HERMES_HOME/webhook_subscriptions.json (per-route HMAC secrets)
  - HERMES_HOME/mcp-tokens/                (OAuth token directory; dir
                                            + everything inside)

…AND iterates over both _hermes_home_path() AND _hermes_root_path()
so profile-mode runs (HERMES_HOME = <root>/profiles/<name>) also block
<root>/{auth.json, .env, mcp-tokens/, ...}. Same widening shape as the
write-deny side already does (NousResearch#15981, NousResearch#14157).

Explicitly NOT a security boundary. Per the personal-assistant trust
model, the terminal tool runs as the same OS user and can `cat
auth.json` directly. This read-deny exists as defense-in-depth:

  - Models that respect tool denials empirically tend to stop rather
    than reach for the shell.
  - The denial surfaces an audit trail when something tries to read
    credentials — easier to spot in logs than a generic `cat`.

Docstring + error message both flag this as defense-in-depth so future
contributors don't mistake it for a real security boundary and don't
re-decline reports that propose the same fix shape.

Absorbs the .env and mcp-tokens/ coverage from @tomqiaozc's parallel
PR NousResearch#8055 (closed-as-duplicate, credited).

Co-authored-by: Tom Qiao <zqiao@microsoft.com>
mosaiq-systems pushed a commit to mosaiq-systems/hermes-agent that referenced this pull request May 29, 2026
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…ts, root

Extends @briandevans's PR NousResearch#17659 from {auth.json, auth.lock,
.anthropic_oauth.json} to also cover:

  - HERMES_HOME/.env                       (provider API keys)
  - HERMES_HOME/webhook_subscriptions.json (per-route HMAC secrets)
  - HERMES_HOME/mcp-tokens/                (OAuth token directory; dir
                                            + everything inside)

…AND iterates over both _hermes_home_path() AND _hermes_root_path()
so profile-mode runs (HERMES_HOME = <root>/profiles/<name>) also block
<root>/{auth.json, .env, mcp-tokens/, ...}. Same widening shape as the
write-deny side already does (NousResearch#15981, NousResearch#14157).

Explicitly NOT a security boundary. Per the personal-assistant trust
model, the terminal tool runs as the same OS user and can `cat
auth.json` directly. This read-deny exists as defense-in-depth:

  - Models that respect tool denials empirically tend to stop rather
    than reach for the shell.
  - The denial surfaces an audit trail when something tries to read
    credentials — easier to spot in logs than a generic `cat`.

Docstring + error message both flag this as defense-in-depth so future
contributors don't mistake it for a real security boundary and don't
re-decline reports that propose the same fix shape.

Absorbs the .env and mcp-tokens/ coverage from @tomqiaozc's parallel
PR NousResearch#8055 (closed-as-duplicate, credited).

Co-authored-by: Tom Qiao <zqiao@microsoft.com>
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
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/agent Core agent loop, run_agent.py, prompt builder P0 Critical — data loss, security, crash loop tool/file File tools (read, write, patch, search) type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: read_file can exfiltrate credentials from auth.json and .anthropic_oauth.json

4 participants