Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions gateway/platforms/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,12 +1134,18 @@ def _media_delivery_denied_paths() -> List[Path]:
# Bitwarden Secrets Manager plaintext disk cache.
os.path.join("cache", "bws_cache.json"),
)
# Directory trees whose every child is credential material. (MCP OAuth
# tokens under mcp-tokens/ are handled by the sibling targeted PR #37222;
# session/kanban SQLite stores by #41071 — kept out of this diff to avoid
# overlap.)
# Directory trees whose every child is credential material.
#
# mcp-tokens/ holds live MCP OAuth access tokens (<server>.json) and
# dynamically-registered client credentials (<server>.client.json); see
# tools/mcp_oauth.py. Same credential class as auth.json/credentials/.
# The write side already denies it (file_tools _check_sensitive_path);
# this pairs the media-delivery (exfil) side so a prompt-injection MEDIA
# tag can't deliver a live bearer token as a native attachment.
# (session/kanban SQLite stores are handled by #41071 — kept out here.)
_ROOT_CREDENTIAL_DIRS = (
"pairing",
"mcp-tokens",
)
for hermes_root in (_HERMES_HOME, _HERMES_ROOT):
for rel in _ROOT_CREDENTIAL_FILES:
Expand Down
32 changes: 32 additions & 0 deletions tests/gateway/test_platform_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,38 @@ def test_denylist_blocks_hermes_credentials(self, tmp_path, monkeypatch):

assert BasePlatformAdapter.validate_media_delivery_path(str(env_file)) is None

@pytest.mark.parametrize(
"rel",
[
"mcp-tokens/github.json",
"mcp-tokens/github.client.json",
"mcp-tokens/github.meta.json",
],
)
def test_denylist_blocks_mcp_oauth_tokens(self, tmp_path, monkeypatch, rel):
"""Live MCP OAuth tokens/client creds under ~/.hermes/mcp-tokens/ must
never deliver as native media — same exfil class as auth.json/.env.
Sibling to the pairing/ directory denylist entry.
"""
self._patch_roots(monkeypatch)

fake_home = tmp_path / "home"
hermes_dir = fake_home / ".hermes"
(hermes_dir / "mcp-tokens").mkdir(parents=True)
secret = hermes_dir / rel
secret.write_text('{"access_token": "live-bearer-abc123"}')
monkeypatch.setenv("HOME", str(fake_home))
monkeypatch.setattr(
"gateway.platforms.base._HERMES_HOME",
hermes_dir,
)
monkeypatch.setattr(
"gateway.platforms.base._HERMES_ROOT",
hermes_dir,
)

assert BasePlatformAdapter.validate_media_delivery_path(str(secret)) is None

def test_denylist_blocks_hermes_config_in_active_profile(self, tmp_path, monkeypatch):
"""The active profile config stays blocked in default mode."""
self._patch_roots(monkeypatch)
Expand Down
Loading