Skip to content

Security: MCP HTTP surface has no auth; multi-agent isolation is unenforced #13

Description

@codenamekt

Summary

The MCP server's multi-agent isolation is a convention, not a control. On the HTTP transport there is no authentication, and agent identity is entirely client-asserted, so any network-adjacent client can read, write, and delete every agent's memories.

Findings

No authentication on the HTTP transport (critical)

mcp_server/server.py:386, mcp_server/cli.py:169FastMCP(name=..., host="0.0.0.0", port=8000) is created with no auth middleware, no token check, and no header inspection. The CLI defaults --host to 0.0.0.0.

  • Scenario: any process that can reach port 8000 calls every tool — memory_recall with agent_identity="" searches all agents, plus arbitrary writes/deletes.

Agent identity is purely client-asserted (critical)

X-Hermes-Session-Key is only referenced in a comment (hexus/__init__.py:533); nothing in server.py/tools.py extracts an HTTP header or binds a connection to an identity. Every tool takes agent_identity as a plain caller-chosen string.

  • Scenario: memory_forget(id=…, confirm=true, agent_identity="agent-b") deletes agent B's rows. The docstring claim "you can only delete rows you own" (server.py:727-732) is false.

Unscoped destructive / read-by-id tools (critical)

  • memory_cleanup (server.py:949-969) → store.cleanup_stale_records (store.py:1746) issues DELETE FROM memory_entries WHERE updated_at < %s with no agent filter and no confirm gate. One call deletes every agent's old memories fleet-wide.
  • memory_retrieve/headroom_retrieve (tools.py:912-929) → store.fetch_full(id) (store.py:1298) selects by sequential BIGSERIAL id only. Enumerate id=1..N to dump every agent's content.
  • memory_confirm/memory_reject and memory_summarize_session share the same by-id, unscoped pattern (cross-agent feedback poisoning → drives consolidation/deletion; cross-agent session reads).

Unauthenticated /metrics leaks the agent roster (high)

server.py:1180-1190 + _generate_metrics (server.py:69-220) emit per-agent-labelled series (hexus_memory_entries_count{agent_identity="…"}, delegation/recall counts). Any client can curl /metrics and enumerate every agent plus their data volumes.

agent_identity default asymmetry (medium, security-relevant)

Empty agent_identity means "this agent" in memory_search/memory_count (tools.py:291,476) but "ALL agents" in memory_recall (tools.py:232-234). The same input has opposite scoping depending on the tool.

Suggested direction

  • Add an auth layer on the HTTP transport (bearer token / API key middleware), or bind to loopback by default and document a reverse-proxy for exposure.
  • Derive agent_identity from an authenticated credential (session key/token) rather than trusting the tool argument; treat the argument as a scope hint within the caller's own identity.
  • Scope fetch_full/confirm_entry/reject_entry/cleanup_stale_records/session reads by agent_identity.
  • Gate memory_cleanup behind confirm + scoping; gate /metrics behind auth.
  • Make empty-agent_identity semantics consistent across tools.

Filed from the 2026-07 codebase review.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: securityArea: securitybugSomething isn't workingcode-reviewFiled from the 2026-07 codebase reviewpriority: criticalMust fix; data loss, security, or broken release

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions