Skip to content

Profile-pinned agent tokens still enumerate out-of-pin servers via /status, /events, /review, /attention, /profiles, /annotations/coverage #1686

Closed

Description

@Dumbris

Problem

A profile-pinned agent token (mcpproxy token create --profile <p>) takes its scope from the profile, so its AllowedServers is usually "*". PR #1684 holds such a token to its pin on GET /api/v1/servers, and GET /tools, /servers?profile= and preflight already apply the pin. Every other read path still gates on canSeeServer → CanEnumerateServer, which checks only AllowedServers. So the same token can still learn the names and counts of servers outside its pin:

Endpoint What leaks Where
GET /api/v1/status upstream_stats.servers.<name> and aggregate server/tool counts internal/httpapi/scope.go (status filter, ~L88)
GET /events (SSE) the servers.changed embed (rows + stats), status frames, server-named events internal/httpapi/server.go (servers.changed scoping, ~L4726), internal/httpapi/sse_scope.go (eventVisibleToCaller)
GET /api/v1/annotations/coverage server names + tool counts internal/httpapi/server.go (~L7581)
GET /api/v1/review quarantined / pending-tool servers + queue count internal/httpapi/review.go:47
GET /api/v1/attention (+ attention.changed) attention items naming the server, dashboard count internal/httpapi/attention.go:112
GET /api/v1/profiles, /profiles/{name} other profiles' servers / effective_servers / server-qualified rules internal/httpapi/profiles.go:68, internal/httpapi/profiles_views.go

Repro: servers a and b, profile prof = [a], then mcpproxy token create --profile prof. GET /api/v1/servers with that token lists only a (after #1684), but GET /api/v1/status and the /events stream still name b.

Decision needed first: is the pin a visibility boundary?

The comment at the top of internal/httpapi/scope_subtree.go says that canSeeServer (token AllowedServers) is the boundary, that the active profile is an ergonomic filter, and that only preflight intersects the pin. Since #1437 and #1682, though, /tools and /servers do treat the operator-set pin as a boundary, so the codebase now gives two answers.

  • Option A (pin is a boundary): intersect the pinned profile's effective servers into the shared enumeration predicate (canSeeServer / visibleServers / CanEnumerateServer), fail closed when the pin names a missing profile, and recompute every derived count. The operator sets the pin and the caller cannot select it, so this can only narrow. The caller-selected ?profile= view-as stays a further narrowing. Switchable-mode credentials need care: their ProfilePin is the binding base, not the full reachable set.
  • Option B (pin stays ergonomic): keep AllowedServers as the only boundary, document that pinned tokens should also be created with --servers, and consider reverting the per-endpoint pin filters so the endpoints agree.

Acceptance

  • The decision is recorded in scope_subtree.go and docs/features/agent-tokens.md.
  • Under option A, one shared predicate is used, and a regression test with a "*"-scoped pinned token covers every endpoint in the table (including the SSE servers.changed embed).

Found while reviewing #1684 (cross-reviewed with opencode gpt-6.1-sol).

Activity

  1. added
    help wantedExtra attention is needed
    area/proxyCore MCP proxy engine and tool routing
    triage/needs-triageNewly opened; not yet reviewed by a maintainer
    on Oct 20, 2026
  2. Dumbris commented on Nov 8, 2026

    @Dumbris
    MemberAuthor

    Fixed by #1821 (Spec 118): the profile pin is intersected into the shared enumeration predicate, so every REST/SSE read door (/status, /events, /review, /attention, /profiles, /annotations/coverage) answers from the same bound set. Shipped in v0.73.0.

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/proxyCore MCP proxy engine and tool routinghelp wantedExtra attention is neededkind/securitySecurity fix or hardeningtriage/needs-triageNewly opened; not yet reviewed by a maintainer

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions