Skip to content

security(memory): the stdio MCP transport has no per-user identity, so memory.verbatim_search returns every user's chunks to any "memory"-scope token #16727

Description

@mrveiss

Sub-issue of #16654. Discovered while fixing #16701 (the same disclosure via the REST route).

Problem

`mcp/autobot_server.py`'s stdio/JSON-RPC MCP server (`AutoBotMCPServer`) authenticates callers entirely by scope token (`"kb"`/`"memory"`/`"agents"` prefixes, via `_check_scope`) -- there is no per-user identity anywhere in its request handling. `_authenticate`/`_validate_token`/`_validate_redis_token` all resolve to a list of granted scopes, never a user id.

The `memory.verbatim_search` tool (`_memory_verbatim_search`, line ~833) calls `VerbatimStore.search(args.query, session_filter=args.session_filter)` with no user scoping at all. Any caller holding a `"memory"`-scoped token -- a broad, service-level grant shared with every other memory tool -- gets every user's verbatim conversation chunks matching the query.

This is the same shape as `_kb_search`/`_kb_get_document` in the same file, already tracked as `TRACKED_GAP #16666` in `repo_tests/kb_read_visibility_allowlist.py`. Memory/verbatim has no equivalent allowlist mechanism to track it in, hence this issue.

#16701 makes `VerbatimStore.search`'s `user_id` a required argument (no longer optional/omittable) specifically so this gap can't spread silently -- the MCP call site now must pass an explicit `UNSCOPED_ALL_USERS` sentinel and say why, rather than a caller simply forgetting to pass `user_id`. That sentinel is what still needs closing here.

Acceptance criteria

  • Either the stdio MCP transport gets a way to carry per-user identity (a scoped-token-to-user mapping, or an explicit trusted `user_id` argument the tool call must supply, with the calling agent/service responsible for supplying the right one), and `memory.verbatim_search` is scoped by it -- removing the `UNSCOPED_ALL_USERS` sentinel from this call site.
  • Or the tool is gated behind a narrower privilege tier than the general `"memory"` scope (e.g. a `"memory:admin"`-style scope), documented as an explicit admin-equivalent grant, not a default capability of every memory-scoped token.
  • A guard or test pins that `UNSCOPED_ALL_USERS` is used only by this tracked call site, so a future caller can't quietly reach for it to skip scoping.

Refs #16654, #16666 (same shape, different subsystem).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions