Skip to content

fix(security): deny home-root single-file credential stores in media delivery - #51453

Open
r266-tech wants to merge 1 commit into
NousResearch:mainfrom
r266-tech:fix/media-delivery-home-credential-files
Open

fix(security): deny home-root single-file credential stores in media delivery#51453
r266-tech wants to merge 1 commit into
NousResearch:mainfrom
r266-tech:fix/media-delivery-home-credential-files

Conversation

@r266-tech

Copy link
Copy Markdown
Contributor

Problem

build_write_denied_paths (agent/file_safety.py:49-53) already forbids the agent from writing five home-root single-file credential stores:

os.path.join(home, ".netrc"),
os.path.join(home, ".pgpass"),
os.path.join(home, ".npmrc"),
os.path.join(home, ".pypirc"),
os.path.join(home, ".git-credentials"),

But the media-delivery denylist in gateway/platforms/base.py does not cover them. #51055 recently extended that denylist so "the delivery/exfil side can't trail the write side", mirroring the write guard — but it only added the ~/.hermes-root credential files (_ROOT_CREDENTIAL_FILES) and the home credential directories (_MEDIA_DELIVERY_DENIED_HOME_SUBPATHS: .ssh, .aws, …). The five home-root single-file stores above were missed.

In the default (non-strict) media-delivery mode, validate_media_delivery_path accepts any existing file that isn't under a denied prefix (base.py). So MEDIA:~/.git-credentials, MEDIA:~/.netrc, etc. remain auto-deliverable — a prompt-injected or buggy agent can attach a plaintext credential file (Git remote tokens, FTP/HTTP logins, Postgres passwords, npm/PyPI registry tokens) straight into a chat reply. This is the exact read/write-vs-delivery divergence #51055 set out to close, and the same threat model the maintainer accepted for ~/.hermes/google_token.json there.

Fix

Add the five basenames to a new _MEDIA_DELIVERY_DENIED_HOME_FILES tuple and append them (joined to $HOME) inside _media_delivery_denied_paths(), right next to the existing home-credential-directory loop. They are matched by exact path in _path_under_denied_prefix (which already handles resolved == resolved_denied), so:

  • ~/.netrc / ~/.pgpass / ~/.npmrc / ~/.pypirc / ~/.git-credentials are denied.
  • An ordinary file sitting directly in $HOME (not one of these basenames) still delivers — the entries are exact-match, so they don't over-block the user's own home tree.

Scoped to these five home-root credential files only; the per-directory and ~/.hermes-root denials from #51055 (and the sibling targeted PRs #37222 mcp-tokens, #41071 state.db) are untouched.

Tests

tests/gateway/test_platform_base.py:

  • test_denylist_blocks_home_root_credential_files — all five are denied in default mode.
  • test_non_credential_home_file_still_delivers — a normal $HOME file still delivers (exact-match, no over-blocking).

Mirrors the existing test_denylist_still_blocks_credentials / test_denylist_blocks_google_token_default_mode pattern.

…delivery

build_write_denied_paths (agent/file_safety.py) already forbids the agent from
writing ~/.netrc, ~/.pgpass, ~/.npmrc, ~/.pypirc and ~/.git-credentials, but the
media-delivery denylist did not cover them. NousResearch#51055 extended that denylist so the
delivery side can't trail the write side, but only added the ~/.hermes-root
credential files and the home credential directories; these five home-root
single-file stores were missed. In default (non-strict) media-delivery mode any
file not under a denied path is deliverable, so a prompt-injected or buggy agent
could auto-attach a plaintext credential file (Git tokens, FTP/HTTP logins,
Postgres passwords, npm/PyPI registry tokens) to a chat reply.

Add the five basenames to a new _MEDIA_DELIVERY_DENIED_HOME_FILES tuple and
append them (joined to $HOME) in _media_delivery_denied_paths(), matched by
exact path. An ordinary file in $HOME still delivers (exact-match, no
over-blocking). Adds regression tests for denial and non-over-blocking.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening comp/gateway Gateway runner, session dispatch, delivery area/auth Authentication, OAuth, credential pools P2 Medium — degraded but workaround exists sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Jun 23, 2026
@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 15, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused security fix. The premise remains valid on current main: gateway/platforms/base.py:1327-1330 accepts any existing non-denied file in default mode, while the home denylist at gateway/platforms/base.py:1025-1035 contains directories but none of the five exact credential-store basenames. The write guard already treats those stores as sensitive at agent/file_safety.py:49-53.

The proposed exact-path additions match _path_under_denied_prefix()'s equality handling at gateway/platforms/base.py:1224-1235, and the paired ordinary-home-file test preserves the intended non-blanket-deny behavior. The current patch needs manual salvage because it no longer applies at its original context (git apply --check fails at gateway/platforms/base.py:1009), but the required port is limited to the home-file tuple/loop and its tests.

Automated hermes-sweeper review.

@egilewski

Copy link
Copy Markdown
Contributor

looks mergeable

A patch replay onto current main preserves this focused security fix; the GitHub head still needs that mechanical refresh before merge.

Security evidence:

  • trust boundary: Model-authored MEDIA: paths cross from the local host filesystem into gateway-native attachments.
  • source/sink/invariant: The five home-root credential stores already denied by build_write_denied_paths() must also be rejected before validate_media_delivery_path() returns an attachment path.
  • current-main reproduction: Default-mode validation accepted all five credential files and a symlink resolving to .netrc on current main.
  • PR-head or patch-replay validation: The current-main patch replay denied all five direct paths and the symlink, and extract_media() returned no attachments for them.
  • positive/negative cases: An ordinary $HOME/report.md remained deliverable, and all 12 default-mode media-delivery tests passed.
  • residual bypass search: Resolved symlink aliases, exact-path matching, and adjacent credential-deny paths produced no scope blocker.
  • reviewer validation: The new tuple matches the five home-root credential entries in build_write_denied_paths().

Not checked:

  • Full test suite
  • CodeRabbit review

Signed: GPT-5.6-sol-xhigh in Codex

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/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants