fix: enforce toolset/promptset boundary on tools/call and prompts/get#3036
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request introduces ContainsPrompt and ContainsTool methods to the Promptset and Toolset structs, respectively, along with corresponding unit tests. These methods are used to verify that a requested prompt or tool belongs to the current set before global resolution in the MCP handlers. The review feedback points out that the current linear search implementation ($O(N)$) for these lookups could become a performance bottleneck as the number of items grows, suggesting a map-based approach for
duwenxin99
left a comment
There was a problem hiding this comment.
Hi @sjhddh , the change LGTM overall except for the error messages. Please let me know once you fix them and I'll handle the merge process for you. Thank you for contributing!
|
You may want to squash the commits to get rid of the agent account that's missing CLA. |
97edc7f to
edb3e0c
Compare
|
@duwenxin99 Thanks! Fixed both:
Let me know if anything else needs polishing. |
|
@duwenxin99 Thanks for the review! Pushed 1e5a7f2. Switched
Also updated three test files that diffed full |
tools/call and prompts/get resolved tools and prompts via the global resourceMgr without verifying membership in the current toolset or promptset. This allowed clients connected to a scoped toolset to invoke any tool by name, bypassing toolset access boundaries (IDOR). Add ContainsTool/ContainsPrompt membership checks and enforce them in all four MCP protocol versions before delegating to resourceMgr. Return the same "does not exist" error on boundary violation as on a truly missing tool/prompt so clients cannot distinguish the two cases. Fixes googleapis#2755
Addresses review feedback on the boundary-enforcement gate: previously ContainsTool/ContainsPrompt performed a linear scan of ToolNames/PromptNames on every tools/call and prompts/get. For large toolsets/promptsets this is a measurable overhead on a hot path. Introduce unexported toolNameSet/promptNameSet maps, populated during Initialize, and use them for O(1) membership checks. Fall back to the linear scan when the Toolset/Promptset was constructed directly (e.g., in tests that bypass Initialize), preserving the zero-value contract. Test comparisons that diff full Toolset/Promptset structs now use cmp.AllowUnexported / cmpopts.IgnoreUnexported so the private cache doesn't affect equality. All existing tests pass.
1e5a7f2 to
eebbcec
Compare
|
/gcbrun |
|
/gcbrun |
…googleapis#3036) ## Summary - **Fix toolset boundary bypass (IDOR)**: `tools/call` previously resolved tools via `resourceMgr.GetTool()` without verifying the requested tool belongs to the current toolset, allowing clients to invoke any tool by name regardless of their toolset scope. - **Fix promptset boundary bypass**: Same issue existed for `prompts/get` with `resourceMgr.GetPrompt()`. - Add `ContainsTool()` / `ContainsPrompt()` membership checks and enforce them in all four MCP protocol versions (v20241105, v20250326, v20250618, v20251125) before delegating to the global resource manager. Fixes googleapis#2755 ## Test plan - [x] Added unit tests for `Toolset.ContainsTool()` and `Promptset.ContainsPrompt()` (both positive and negative cases, including empty sets) - [x] All existing tests pass (`go test ./internal/tools/... ./internal/prompts/... ./internal/server/mcp/...`) - [ ] Manual verification: configure two toolsets with different tools, connect to one, and confirm `tools/call` rejects tools from the other toolset 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Wenxin Du <117315983+duwenxin99@users.noreply.github.com>
🤖 I have created a release *beep* *boop* --- ## [1.3.0](v1.2.0...v1.3.0) (2026-05-21) ### Features * **auth:** Implement MCP auth tool-level scopes validation ([#3049](#3049)) ([c528985](c528985)) * **looker:** Propagate client IP from incoming MCP requests to downstream SDK calls ([#3253](#3253)) ([75da6c2](75da6c2)) * Setup SQLCommenter and allow client metadata ([#3064](#3064)) ([9f1f9b3](9f1f9b3)) * **tool/cloudsqladmin:** Add `cloud-sql-admin-execute-sql-many` and `cloud-sql-admin-sql-many` ([#3083](#3083)) ([ef300a8](ef300a8)) ### Bug Fixes * **auth/generic:** Fix generic auth expiration field and integration with `authRequired` ([#3251](#3251)) ([f4d16c0](f4d16c0)) * Enforce toolset/promptset boundary on tools/call and prompts/get ([#3036](#3036)) ([c739b80](c739b80)) * **tools/http:** Prevent path traversal and base path scope escape ([#3218](#3218)) ([80a6602](80a6602)) * **tools/looker:** Return a 401 error to MCP client when Looker returns a 401 ([#3233](#3233)) ([4f409a3](4f409a3)) * **tools/looker:** Strip wrapping quotes from filter values for unquoted parameters ([#3273](#3273)) ([1e3de96](1e3de96)) * **tools:** Initialize query result slices to empty array ([#3250](#3250)) ([60ddf48](60ddf48)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
🤖 I have created a release *beep* *boop* --- ## [1.3.0](v1.2.0...v1.3.0) (2026-05-21) ### Features * **auth:** Implement MCP auth tool-level scopes validation ([#3049](#3049)) ([c528985](c528985)) * **looker:** Propagate client IP from incoming MCP requests to downstream SDK calls ([#3253](#3253)) ([75da6c2](75da6c2)) * Setup SQLCommenter and allow client metadata ([#3064](#3064)) ([9f1f9b3](9f1f9b3)) * **tool/cloudsqladmin:** Add `cloud-sql-admin-execute-sql-many` and `cloud-sql-admin-sql-many` ([#3083](#3083)) ([ef300a8](ef300a8)) ### Bug Fixes * **auth/generic:** Fix generic auth expiration field and integration with `authRequired` ([#3251](#3251)) ([f4d16c0](f4d16c0)) * Enforce toolset/promptset boundary on tools/call and prompts/get ([#3036](#3036)) ([c739b80](c739b80)) * **tools/http:** Prevent path traversal and base path scope escape ([#3218](#3218)) ([80a6602](80a6602)) * **tools/looker:** Return a 401 error to MCP client when Looker returns a 401 ([#3233](#3233)) ([4f409a3](4f409a3)) * **tools/looker:** Strip wrapping quotes from filter values for unquoted parameters ([#3273](#3273)) ([1e3de96](1e3de96)) * **tools:** Initialize query result slices to empty array ([#3250](#3250)) ([60ddf48](60ddf48)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> b001006
🤖 I have created a release *beep* *boop* --- ## [1.3.0](googleapis/mcp-toolbox@v1.2.0...v1.3.0) (2026-05-21) ### Features * **auth:** Implement MCP auth tool-level scopes validation ([googleapis#3049](googleapis#3049)) ([c528985](googleapis@c528985)) * **looker:** Propagate client IP from incoming MCP requests to downstream SDK calls ([googleapis#3253](googleapis#3253)) ([75da6c2](googleapis@75da6c2)) * Setup SQLCommenter and allow client metadata ([googleapis#3064](googleapis#3064)) ([9f1f9b3](googleapis@9f1f9b3)) * **tool/cloudsqladmin:** Add `cloud-sql-admin-execute-sql-many` and `cloud-sql-admin-sql-many` ([googleapis#3083](googleapis#3083)) ([ef300a8](googleapis@ef300a8)) ### Bug Fixes * **auth/generic:** Fix generic auth expiration field and integration with `authRequired` ([googleapis#3251](googleapis#3251)) ([f4d16c0](googleapis@f4d16c0)) * Enforce toolset/promptset boundary on tools/call and prompts/get ([googleapis#3036](googleapis#3036)) ([c739b80](googleapis@c739b80)) * **tools/http:** Prevent path traversal and base path scope escape ([googleapis#3218](googleapis#3218)) ([80a6602](googleapis@80a6602)) * **tools/looker:** Return a 401 error to MCP client when Looker returns a 401 ([googleapis#3233](googleapis#3233)) ([4f409a3](googleapis@4f409a3)) * **tools/looker:** Strip wrapping quotes from filter values for unquoted parameters ([googleapis#3273](googleapis#3273)) ([1e3de96](googleapis@1e3de96)) * **tools:** Initialize query result slices to empty array ([googleapis#3250](googleapis#3250)) ([60ddf48](googleapis@60ddf48)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> b001006
🤖 I have created a release *beep* *boop* --- ## [1.3.0](googleapis/mcp-toolbox@v1.2.0...v1.3.0) (2026-05-21) ### Features * **auth:** Implement MCP auth tool-level scopes validation ([googleapis#3049](googleapis#3049)) ([c528985](googleapis@c528985)) * **looker:** Propagate client IP from incoming MCP requests to downstream SDK calls ([googleapis#3253](googleapis#3253)) ([75da6c2](googleapis@75da6c2)) * Setup SQLCommenter and allow client metadata ([googleapis#3064](googleapis#3064)) ([9f1f9b3](googleapis@9f1f9b3)) * **tool/cloudsqladmin:** Add `cloud-sql-admin-execute-sql-many` and `cloud-sql-admin-sql-many` ([googleapis#3083](googleapis#3083)) ([ef300a8](googleapis@ef300a8)) ### Bug Fixes * **auth/generic:** Fix generic auth expiration field and integration with `authRequired` ([googleapis#3251](googleapis#3251)) ([f4d16c0](googleapis@f4d16c0)) * Enforce toolset/promptset boundary on tools/call and prompts/get ([googleapis#3036](googleapis#3036)) ([c739b80](googleapis@c739b80)) * **tools/http:** Prevent path traversal and base path scope escape ([googleapis#3218](googleapis#3218)) ([80a6602](googleapis@80a6602)) * **tools/looker:** Return a 401 error to MCP client when Looker returns a 401 ([googleapis#3233](googleapis#3233)) ([4f409a3](googleapis@4f409a3)) * **tools/looker:** Strip wrapping quotes from filter values for unquoted parameters ([googleapis#3273](googleapis#3273)) ([1e3de96](googleapis@1e3de96)) * **tools:** Initialize query result slices to empty array ([googleapis#3250](googleapis#3250)) ([60ddf48](googleapis@60ddf48)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> b001006
🤖 I have created a release *beep* *boop* --- ## [1.3.0](googleapis/mcp-toolbox@v1.2.0...v1.3.0) (2026-05-21) ### Features * **auth:** Implement MCP auth tool-level scopes validation ([googleapis#3049](googleapis#3049)) ([c528985](googleapis@c528985)) * **looker:** Propagate client IP from incoming MCP requests to downstream SDK calls ([googleapis#3253](googleapis#3253)) ([75da6c2](googleapis@75da6c2)) * Setup SQLCommenter and allow client metadata ([googleapis#3064](googleapis#3064)) ([9f1f9b3](googleapis@9f1f9b3)) * **tool/cloudsqladmin:** Add `cloud-sql-admin-execute-sql-many` and `cloud-sql-admin-sql-many` ([googleapis#3083](googleapis#3083)) ([ef300a8](googleapis@ef300a8)) ### Bug Fixes * **auth/generic:** Fix generic auth expiration field and integration with `authRequired` ([googleapis#3251](googleapis#3251)) ([f4d16c0](googleapis@f4d16c0)) * Enforce toolset/promptset boundary on tools/call and prompts/get ([googleapis#3036](googleapis#3036)) ([c739b80](googleapis@c739b80)) * **tools/http:** Prevent path traversal and base path scope escape ([googleapis#3218](googleapis#3218)) ([80a6602](googleapis@80a6602)) * **tools/looker:** Return a 401 error to MCP client when Looker returns a 401 ([googleapis#3233](googleapis#3233)) ([4f409a3](googleapis@4f409a3)) * **tools/looker:** Strip wrapping quotes from filter values for unquoted parameters ([googleapis#3273](googleapis#3273)) ([1e3de96](googleapis@1e3de96)) * **tools:** Initialize query result slices to empty array ([googleapis#3250](googleapis#3250)) ([60ddf48](googleapis@60ddf48)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> b001006
🤖 I have created a release *beep* *boop* --- ## [1.3.0](googleapis/mcp-toolbox@v1.2.0...v1.3.0) (2026-05-21) ### Features * **auth:** Implement MCP auth tool-level scopes validation ([googleapis#3049](googleapis#3049)) ([c528985](googleapis@c528985)) * **looker:** Propagate client IP from incoming MCP requests to downstream SDK calls ([googleapis#3253](googleapis#3253)) ([75da6c2](googleapis@75da6c2)) * Setup SQLCommenter and allow client metadata ([googleapis#3064](googleapis#3064)) ([9f1f9b3](googleapis@9f1f9b3)) * **tool/cloudsqladmin:** Add `cloud-sql-admin-execute-sql-many` and `cloud-sql-admin-sql-many` ([googleapis#3083](googleapis#3083)) ([ef300a8](googleapis@ef300a8)) ### Bug Fixes * **auth/generic:** Fix generic auth expiration field and integration with `authRequired` ([googleapis#3251](googleapis#3251)) ([f4d16c0](googleapis@f4d16c0)) * Enforce toolset/promptset boundary on tools/call and prompts/get ([googleapis#3036](googleapis#3036)) ([c739b80](googleapis@c739b80)) * **tools/http:** Prevent path traversal and base path scope escape ([googleapis#3218](googleapis#3218)) ([80a6602](googleapis@80a6602)) * **tools/looker:** Return a 401 error to MCP client when Looker returns a 401 ([googleapis#3233](googleapis#3233)) ([4f409a3](googleapis@4f409a3)) * **tools/looker:** Strip wrapping quotes from filter values for unquoted parameters ([googleapis#3273](googleapis#3273)) ([1e3de96](googleapis@1e3de96)) * **tools:** Initialize query result slices to empty array ([googleapis#3250](googleapis#3250)) ([60ddf48](googleapis@60ddf48)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> b001006
🤖 I have created a release *beep* *boop* --- ## [1.3.0](googleapis/mcp-toolbox@v1.2.0...v1.3.0) (2026-05-21) ### Features * **auth:** Implement MCP auth tool-level scopes validation ([googleapis#3049](googleapis#3049)) ([c528985](googleapis@c528985)) * **looker:** Propagate client IP from incoming MCP requests to downstream SDK calls ([googleapis#3253](googleapis#3253)) ([75da6c2](googleapis@75da6c2)) * Setup SQLCommenter and allow client metadata ([googleapis#3064](googleapis#3064)) ([9f1f9b3](googleapis@9f1f9b3)) * **tool/cloudsqladmin:** Add `cloud-sql-admin-execute-sql-many` and `cloud-sql-admin-sql-many` ([googleapis#3083](googleapis#3083)) ([ef300a8](googleapis@ef300a8)) ### Bug Fixes * **auth/generic:** Fix generic auth expiration field and integration with `authRequired` ([googleapis#3251](googleapis#3251)) ([f4d16c0](googleapis@f4d16c0)) * Enforce toolset/promptset boundary on tools/call and prompts/get ([googleapis#3036](googleapis#3036)) ([c739b80](googleapis@c739b80)) * **tools/http:** Prevent path traversal and base path scope escape ([googleapis#3218](googleapis#3218)) ([80a6602](googleapis@80a6602)) * **tools/looker:** Return a 401 error to MCP client when Looker returns a 401 ([googleapis#3233](googleapis#3233)) ([4f409a3](googleapis@4f409a3)) * **tools/looker:** Strip wrapping quotes from filter values for unquoted parameters ([googleapis#3273](googleapis#3273)) ([1e3de96](googleapis@1e3de96)) * **tools:** Initialize query result slices to empty array ([googleapis#3250](googleapis#3250)) ([60ddf48](googleapis@60ddf48)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> b001006
🤖 I have created a release *beep* *boop* --- ## [1.3.0](googleapis/mcp-toolbox@v1.2.0...v1.3.0) (2026-05-21) ### Features * **auth:** Implement MCP auth tool-level scopes validation ([googleapis#3049](googleapis#3049)) ([c528985](googleapis@c528985)) * **looker:** Propagate client IP from incoming MCP requests to downstream SDK calls ([googleapis#3253](googleapis#3253)) ([75da6c2](googleapis@75da6c2)) * Setup SQLCommenter and allow client metadata ([googleapis#3064](googleapis#3064)) ([9f1f9b3](googleapis@9f1f9b3)) * **tool/cloudsqladmin:** Add `cloud-sql-admin-execute-sql-many` and `cloud-sql-admin-sql-many` ([googleapis#3083](googleapis#3083)) ([ef300a8](googleapis@ef300a8)) ### Bug Fixes * **auth/generic:** Fix generic auth expiration field and integration with `authRequired` ([googleapis#3251](googleapis#3251)) ([f4d16c0](googleapis@f4d16c0)) * Enforce toolset/promptset boundary on tools/call and prompts/get ([googleapis#3036](googleapis#3036)) ([c739b80](googleapis@c739b80)) * **tools/http:** Prevent path traversal and base path scope escape ([googleapis#3218](googleapis#3218)) ([80a6602](googleapis@80a6602)) * **tools/looker:** Return a 401 error to MCP client when Looker returns a 401 ([googleapis#3233](googleapis#3233)) ([4f409a3](googleapis@4f409a3)) * **tools/looker:** Strip wrapping quotes from filter values for unquoted parameters ([googleapis#3273](googleapis#3273)) ([1e3de96](googleapis@1e3de96)) * **tools:** Initialize query result slices to empty array ([googleapis#3250](googleapis#3250)) ([60ddf48](googleapis@60ddf48)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> b001006
Summary
tools/callpreviously resolved tools viaresourceMgr.GetTool()without verifying the requested tool belongs to the current toolset, allowing clients to invoke any tool by name regardless of their toolset scope.prompts/getwithresourceMgr.GetPrompt().ContainsTool()/ContainsPrompt()membership checks and enforce them in all four MCP protocol versions (v20241105, v20250326, v20250618, v20251125) before delegating to the global resource manager.Fixes #2755
Test plan
Toolset.ContainsTool()andPromptset.ContainsPrompt()(both positive and negative cases, including empty sets)go test ./internal/tools/... ./internal/prompts/... ./internal/server/mcp/...)tools/callrejects tools from the other toolset🤖 Generated with Claude Code