Skip to content

Commit 4f382af

Browse files
authored
Gateway: enforce allowed-tools filtering server-side on tools/list and tools/call (#3333)
Agents with raw HTTP access to the gateway could bypass client-side `--allowed-tools` filters by directly sending `tools/call` JSON-RPC requests for tools they shouldn't be able to call. The existing `tools` field in `StdinServerConfig`/`ServerConfig` was parsed but never enforced at runtime. ## Changes ### Pre-computed allowed-tools sets (`unified.go`) - Added `allowedToolSets map[string]map[string]bool` to `UnifiedServer`, built once at init via `buildAllowedToolSets(cfg)` for O(1) per-call lookup - Added `isToolAllowed(serverID, toolName)` — returns `true` when no list is configured (unrestricted) ### Enforcement in `callBackendTool` (`unified.go`) Before any DIFC/guard work, rejects calls for tools not in the allowed set: - Returns `IsError: true` `CallToolResult` with a descriptive message (`"tool %q is not in the allowed-tools list for this server"`) - Sets OTEL span HTTP status to 403 - Logs at WARN with `logger.LogWarn("client", ...)` including the server ID ### tools/list defense-in-depth (`tool_registry.go`) During backend tool registration, non-allowed tools are filtered out using the pre-computed `allowedToolSets` — they never appear in `tools/list` responses and are never registered with the SDK server. ### Config usage The existing `tools` field on each MCP server entry is the allow-list: ```json { "mcpServers": { "github": { "type": "stdio", "container": "ghcr.io/github/github-mcp-server:latest", "tools": ["search_code", "get_file_contents", "list_issues"] } } } ``` When `tools` is empty or absent, all tools remain accessible (no behavior change for existing configs). ## Testing A dedicated `internal/server/allowed_tools_integration_test.go` provides 11 integration tests covering the full enforcement path end-to-end: - **tools/list filtering**: unified server with a single backend, multiple independent backends, and no-restriction passthrough - **tools/call enforcement**: allowed tool executes successfully; blocked tool returns `IsError: true` with the backend verified to *not* receive the forwarded request; unrestricted server passes all tools - **Routed mode**: filtered tools/list and blocked call handler not registered - **Helpers**: `buildAllowedToolSets` with multiple servers and empty/nil config; `isToolAllowed` with real config Additional unit tests in `call_backend_tool_test.go` and `tool_registry_test.go` cover the `isToolAllowed` helper, `callBackendTool` rejection, and registration filtering.
2 parents 8c39c72 + 2cb5103 commit 4f382af

File tree

5 files changed

+847
-0
lines changed

5 files changed

+847
-0
lines changed

0 commit comments

Comments
 (0)