feat: enforce VK tool-grant boundary on caller-provided x-bf-mcp-include-tools lists via pruneMCPIncludeToolsFromContext - #4296
Conversation
|
|
📝 WalkthroughWalkthroughPrunes caller-provided MCP ChangesMCP Include-Tools Pruning
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" Comment |
x-bf-mcp-include-tools can only narrow VK tool grant via pruneMCPIncludeToolsFromContextx-bf-mcp-include-tools lists via pruneMCPIncludeToolsFromContext
|
@coderabbitai full-review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Get the PR diff and key file contents
gh pr view 4296 --json files,title,body
echo "=== main.go changes ==="
gh api repos/maximhq/bifrost/pulls/4296/files | jq -r '.[] | select(.filename | contains("governance/main.go")) | .patch'
echo "=== new test file ==="
gh api repos/maximhq/bifrost/pulls/4296/files | jq -r '.[] | select(.filename | contains("prunemcpincludetools_test.go")) | .patch'
echo "=== full file list ==="
gh api repos/maximhq/bifrost/pulls/4296/files | jq -r '.[].filename'Length of output: 18013 🏁 Script executed: #!/bin/bash
# Read current full contents of the modified files
cat plugins/governance/main.goLength of output: 70978 🏁 Script executed: #!/bin/bash
cat plugins/governance/prunemcpincludetools_test.go 2>/dev/null || echo "File not found in current branch"
# Also check related schemas
rg -n "MCPContextKeyIncludeTools" --type=go -A2 -B2
# Check computeMCPIncludeTools
rg -n "computeMCPIncludeTools" --type=go -A20 -B2Length of output: 28462
Review of
|
|
This suggestion is solving a problem that doesn't exist, and the sketch as written would reintroduce the leak we just fixed. Two issues:
func (p *GovernancePlugin) pruneMCPIncludeToolsFromContext(...) bool { When a caller list exists, prune computes once and the inject branch is skipped (!includeToolsProvided is false). When it doesn't, prune returns before computing and the inject branch computes once. Exactly one computation per request either way. The suggested version actually makes it worse: it computes eagerly even on the path where nothing is stamped at all (auto-inject off, no filters) — wasted work on every such request.
If this came from a review bot, I'd resolve it with that explanation rather than apply it. If what you like about it is the signature — making the data flow explicit instead of the boolean — a corrected version would be: includeToolsProvided, vkTools := p.pruneMCPIncludeToolsFromContext(ctx, virtualKey) with prune computing vkTools up front. That's behaviorally safe but trades the lazy computation for a slightly cleaner call site. My recommendation is to leave the code as is — say the word if you'd like the corrected variant applied instead. |
|
Tip For best results, initiate chat on the files or code changes.
The code as-is is correct. I'm withdrawing the review comment. 🐇 (ears-down moment) |
Confidence Score: 5/5Safe to merge. The change tightens an access-control boundary and fails closed; the refactor to The pruning logic covers all four relevant paths — specific tools, wildcard expansion, No files require special attention. Important Files Changed
Reviews (2): Last reviewed commit: "fix: governance mcp tool filter fixes" | Re-trigger Greptile |
9c77480 to
1ee84a3
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/governance/prunemcpincludetools_test.go`:
- Around line 31-187: Add two low-priority edge-case tests for
pruneMCPIncludeToolsFromContext: one that passes a nil virtualKey to verify it
fails closed (returns true and results in an empty include-tools ctx) to
document the defensive contract, and another that uses a client name containing
multiple dashes (e.g., "sentry-mcp") with a wildcard request like "sentry-mcp-*"
to ensure the suffix-cutting logic preserves the multi-dash prefix; reuse
helpers newPluginWithInMemoryStore, newCtxWithIncludeTools,
buildVKWithMCPConfigs or construct a TableVirtualKey as in
TestPruneMCPIncludeTools_MultipleClients, and assert behavior via
includeToolsFromCtx and the boolean return of pruneMCPIncludeToolsFromContext.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e0911dc6-7da9-40de-bc1e-9bf59a42fc5e
📒 Files selected for processing (2)
plugins/governance/main.goplugins/governance/prunemcpincludetools_test.go
Merge activity
|
…lude-tools` lists via `pruneMCPIncludeToolsFromContext` (#4296) ## Summary Caller-provided MCP include-tools lists (set via the `x-bf-mcp-include-tools` header) could previously reference tools that the virtual key does not grant, effectively allowing callers to expand their own tool access beyond what the key permits. This PR enforces that a caller-provided list can only narrow the virtual key's tool grant, never expand it. ## Changes - Introduced `pruneMCPIncludeToolsFromContext`, which reads a caller-provided include-tools list from the context, intersects it with the tools the virtual key actually grants, and writes the pruned result back to the context. - Caller wildcards (e.g. `sentry-*`) are kept verbatim only when the virtual key itself is unrestricted for that client; otherwise they are expanded to the key's specific grants to prevent the wildcard from being interpreted downstream as "all tools for this client." - Auto-injection of the computed tool list is now skipped when a caller-provided list was present (even if it pruned to empty), replacing the previous check that only skipped injection when the context key was unset. - Pruning is applied in both `runPreRequestRouting` and `PreRequestHook`, replacing the earlier pattern of checking for an existing context value before injecting. - A wrong-type context value fails closed, pruning to a deny-all empty list rather than passing through. - Added a dedicated test file covering: no caller list, disallowed tools dropped, granted tools kept, specific tools under unrestricted grants, wildcard passthrough vs. narrowing, ungranted client wildcards, empty header opt-out, deduplication, `AllowOnAllVirtualKeys` clients, explicit empty config overriding allow-all, multi-client pruning, and wrong-type fail-closed behavior. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./plugins/governance/... ``` The new test file `prunemcpincludetools_test.go` covers all pruning scenarios. Verify that: - A caller requesting a tool not in the virtual key's grant receives an empty (deny-all) list. - A caller requesting a subset of granted tools receives only that subset. - A caller wildcard is narrowed to the key's specific grants when the key is not itself unrestricted for that client. - Auto-injection does not overwrite a caller-provided list, even when the pruned result is empty. ## Breaking changes - [ ] Yes - [x] No ## Security considerations This change closes a privilege escalation path where a caller could use the `x-bf-mcp-include-tools` header to request tools beyond what their virtual key grants. The pruning logic ensures the effective tool set is always bounded by the virtual key's configured permissions, and any ambiguous or malformed input fails closed to a deny-all state. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Caller-provided MCP include-tools lists are now pruned to only the tools allowed for the active virtual key; auto-injection of tools occurs only when callers provide no list and auto-injection isn’t disabled. * **Tests** * Added comprehensive tests covering pruning behavior, wildcards, deduplication, opt-out cases, multi-client scenarios, and fail-closed handling. <!-- end of auto-generated comment: release notes by coderabbit.ai -->

Summary
Caller-provided MCP include-tools lists (set via the
x-bf-mcp-include-toolsheader) could previously reference tools that the virtual key does not grant, effectively allowing callers to expand their own tool access beyond what the key permits. This PR enforces that a caller-provided list can only narrow the virtual key's tool grant, never expand it.Changes
pruneMCPIncludeToolsFromContext, which reads a caller-provided include-tools list from the context, intersects it with the tools the virtual key actually grants, and writes the pruned result back to the context.sentry-*) are kept verbatim only when the virtual key itself is unrestricted for that client; otherwise they are expanded to the key's specific grants to prevent the wildcard from being interpreted downstream as "all tools for this client."runPreRequestRoutingandPreRequestHook, replacing the earlier pattern of checking for an existing context value before injecting.AllowOnAllVirtualKeysclients, explicit empty config overriding allow-all, multi-client pruning, and wrong-type fail-closed behavior.Type of change
Affected areas
How to test
go test ./plugins/governance/...The new test file
prunemcpincludetools_test.gocovers all pruning scenarios. Verify that:Breaking changes
Security considerations
This change closes a privilege escalation path where a caller could use the
x-bf-mcp-include-toolsheader to request tools beyond what their virtual key grants. The pruning logic ensures the effective tool set is always bounded by the virtual key's configured permissions, and any ambiguous or malformed input fails closed to a deny-all state.Checklist
docs/contributing/README.mdand followed the guidelinesSummary by CodeRabbit
New Features
Tests