Skip to content

fix(security): shared agent-token operation policy — REST server mutations 403 for agents; index/search honors quarantine (#878, #877)#888

Merged
github-actions[bot] merged 4 commits into
mainfrom
fix/878-877-agent-token-gating
Jul 22, 2026
Merged

fix(security): shared agent-token operation policy — REST server mutations 403 for agents; index/search honors quarantine (#878, #877)#888
github-actions[bot] merged 4 commits into
mainfrom
fix/878-877-agent-token-gating

Conversation

@Dumbris

@Dumbris Dumbris commented Jul 22, 2026

Copy link
Copy Markdown
Member

Threat model

Agent tokens (mcp_agt_) authenticate against the REST API but are meant to be scoped, read-oriented — the MCP surface already denies them server administration. Two gaps let them (or any REST caller) exceed that boundary:

Fix

  1. One shared policy (internal/auth): AgentDeniedServerOp / AuthorizeServerOp name the server/tool-mutating operations denied to agent tokens. Consumed by both the MCP upstream_servers denylist and a REST requireServerOp wrapper, so the two surfaces cannot drift again. The MCP denylist behavior is byte-preserved (list/tail_log still allowed).
  2. REST enforcement: every mutating /servers route (add/remove/patch/enable/disable/restart/reconnect/quarantine/unquarantine/login/logout/config-to-secret/discover-tools/refresh, tool approve/block, and the *_all bulk routes) returns 403 operation requires admin access for an agent token. Full API keys and OS-authenticated socket (tray) connections are unaffected.
  3. Quarantine filter: server.SearchTools applies the same server-level visibility gate retrieve_tools relies on, reading authoritative quarantine state from storage. Response shape is unchanged — bare tool name + separate server_name (the #871/mcp-eval contract); only which tools appear is filtered.

Tests

  • Table-driven internal/httpapi test iterating every mutating server route: agent token → 403, admin key → next-layer (back-compat).
  • internal/auth policy unit tests.
  • Deterministic TestE2E_IndexSearchHonorsQuarantine (index-then-quarantine) — Bleve still holds the entry, SearchTools returns 0; fails if the filter is removed.
  • TestE2E_QuarantineConfigApply step-4 assertion rewritten to grep server_name (what the REST surface returns) instead of a server: prefix it never emits — the old check was vacuously true and masked the leak.

OpenAPI (oas/swagger.yaml) + docs (docs/api/rest-api.md, docs/features/agent-tokens.md) document the 403 behavior and the search quarantine gate.

Fixes #878
Fixes #877


Bypass-route hardening (cross-model review)

A shared policy is only as good as its coverage. Cross-model review surfaced sibling routes through which an agent could reach the same server/security/config/credential state without touching /servers. All are now gated through the same requireServerOp wrapper + internal/auth policy:

  • POST /config/apply, PATCH /config, PATCH /config/docker-isolation (config can add/remove/enable/disable servers)
  • Registry source CRUD (POST/PUT/DELETE /registries[/{id}]) and the keystone add (POST /registries/{id}/servers/{serverId}/add)
  • Security scanner — per-server (scan, scan/cancel, security/approve, security/reject) and global (scanners/{id}/{enable,disable,config}, scanners/install, DELETE scanners/{id}, scan-all, cancel-all)
  • Secrets write/delete/migrate (keyring credentials; restarts affected servers)
  • POST /diagnostics/fix (executes admin fixers — OAuth reauth, scanner disable, config migration)
  • POST /connect/{client} + /undo + DELETE (write local client config, can embed the admin API key)

Token management (/api/v1/tokens*) was already admin-gated (requireAdminAuth). The index/search quarantine filter fails closed (an unresolvable server name, nil storage, or a lookup error withholds the hit).

…search honors quarantine

Two related gaps let an agent token (mcp_agt_) do over REST what it is denied
over MCP, and let quarantined servers' tool descriptions leak via REST search.

#878 — REST mutating /api/v1/servers/{id} routes (enable/disable/restart/
quarantine/unquarantine/patch/remove/login/logout/config-to-secret/refresh/
discover-tools, tool approve/block, and the top-level add/import/reconnect/
*_all bulk routes) had no agent-token gate, while the MCP upstream_servers /
quarantine_security tools deny them. Extract one shared policy
(internal/auth.AuthorizeServerOp + AgentDeniedServerOp) consumed by BOTH the MCP
denylist and a REST requireServerOp wrapper, so the surfaces can't drift. Agent
tokens now get 403 "operation requires admin access"; admin keys and OS-auth
socket connections are unaffected.

#877 — GET /api/v1/index/search served the tools of server-quarantined upstreams
(their descriptions are the TPA payload quarantine withholds). server.SearchTools
now applies the same server-level visibility gate retrieve_tools relies on,
reading authoritative quarantine state from storage; response shape is unchanged
(bare tool name + separate server_name), only which tools appear is filtered.

Tests: table-driven httpapi test over every mutating route (agent=403,
admin=next-layer); auth policy unit tests; a deterministic E2E reproduction
(index-then-quarantine) that fails if the REST filter is removed; and the
previously-vacuous TestE2E_QuarantineConfigApply step-4 assertion rewritten to
grep server_name (what the surface returns) instead of a prefix it never emits.

OpenAPI + docs (rest-api.md, agent-tokens.md) document the 403 behavior and the
search quarantine gate.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 22, 2026

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: ad7b4f4
Status: ✅  Deploy successful!
Preview URL: https://4dbec464.mcpproxy-docs.pages.dev
Branch Preview URL: https://fix-878-877-agent-token-gati.mcpproxy-docs.pages.dev

View logs

…d search filter

Codex round-1 review of PR #888 found three genuine gaps:

- Agents could bypass the /servers gate via sibling mutating routes:
  POST /config/apply and PATCH /config[/docker-isolation] (config can
  add/remove/enable/disable servers), the registry keystone add
  (add_from_registry equivalent), registry source CRUD, and the per-server
  security scanner (scan start/cancel, security approve/reject). All now route
  through requireServerOp with new shared-policy op ids (config_write, scan,
  security_approve/reject).
- The index/search quarantine filter failed OPEN on an empty server name, nil
  storage, or a storage error — a stale/legacy index entry or transient BBolt
  failure could expose withheld descriptions. It now fails CLOSED: a hit is
  withheld unless positively attributed to a resolvable, non-quarantined server.

Table-driven httpapi test extended to cover the new routes; docs + OpenAPI 403s
updated.
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: fix/878-877-agent-token-gating

Available Artifacts

  • archive-darwin-amd64 (28 MB)
  • archive-darwin-arm64 (25 MB)
  • archive-linux-amd64 (16 MB)
  • archive-linux-arm64 (15 MB)
  • archive-windows-amd64 (28 MB)
  • archive-windows-arm64 (25 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (22 MB)
  • installer-dmg-darwin-arm64 (20 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 29900225671 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

Dumbris added 2 commits July 22, 2026 10:24
…es for agents

Codex round-2 review of PR #888 found three more agent-token-reachable admin
mutation surfaces outside /servers:

- Secrets: POST /secrets, DELETE /secrets/{name}, POST /secrets/migrate — write
  or delete keyring credentials and restart affected servers.
- Global security scanner: enable/disable/config/install/remove scanners and
  scan-all/cancel-all batch runs.
- POST /diagnostics/fix — executes administrative fixers (OAuth reauth, scanner
  disable, config migration).

All now route through requireServerOp with shared-policy op ids (secret_write,
scan, diagnostics_fix). Token management was already admin-gated
(requireAdminAuth) and needs no change. Table test + policy test extended.
Codex round-3 review of PR #888: POST /connect/{client}, its /undo, and
DELETE /connect/{client} write, restore, or delete local MCP client config files
and may embed the admin API key into that config. An agent token could trigger
them to exfiltrate the admin key or mutate client config. Gate all three via
requireServerOp (config_write); status/preview reads stay open. Table test
extended.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved: Codex cross-model review clean; API E2E pass at this SHA; qa-gate blessed. Arming auto-merge.

@github-actions
github-actions Bot enabled auto-merge (squash) July 22, 2026 07:52
@github-actions
github-actions Bot merged commit f07ba7a into main Jul 22, 2026
40 of 41 checks passed
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 77.01149% with 20 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/server/server.go 0.00% 20 Missing ⚠️

📢 Thoughts on this report? Let us know!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

REST mutating server endpoints lack agent-token gating (enable/disable/restart/quarantine/…) /api/v1/index/search bypasses quarantine filtering

2 participants