Skip to content

🔏 fix: Enforce MCP Server Authorization on Agent Tool Persistence#12250

Merged
danny-avila merged 5 commits into
devfrom
fix/mcp-agent-tool-authz-bypass
Mar 16, 2026
Merged

🔏 fix: Enforce MCP Server Authorization on Agent Tool Persistence#12250
danny-avila merged 5 commits into
devfrom
fix/mcp-agent-tool-authz-bypass

Conversation

@danny-avila

@danny-avila danny-avila commented Mar 15, 2026

Copy link
Copy Markdown
Owner

Summary

Closes an authorization bypass where agent create, update, duplicate, and revert paths accepted arbitrary MCP tool strings without verifying the requesting user's access to the referenced MCP servers. A crafted identifier like anything_mcp_<targetServer> would persist targetServer into the agent's mcpServerNames array, granting consumeOnly access via hasAccessViaAgent() to a server the user was never authorized to use.

  • Adds filterAuthorizedTools() that validates each MCP tool string against the user's accessible server configs via getMCPServersRegistry().getAllServerConfigs(userId) before persisting; non-MCP tools continue to be validated against the global tool cache and systemTools as before.
  • Wraps registry access in a try/catch so instances without MCP configured gracefully reject all MCP tool strings instead of propagating an uninitialized singleton error as a 500.
  • Implements single-pass lazy-fetch: the registry is queried at most once per call, only upon encountering the first MCP tool string, with undefined as the unfetched sentinel.
  • Guards updateAgentHandler by diffing incoming tools against existingAgent.tools — only newly added MCP tools are validated, preserving tools already on the agent that a collaborating editor may not have direct access to.
  • Guards revertAgentVersionHandler: filters unauthorized MCP tools from the restored version snapshot and re-persists via updateAgent when tools are stripped, ensuring the response reflects the committed DB state.
  • Emits logger.warn for every rejected MCP tool (including tool name, server name, and user ID) and on registry unavailability, establishing an audit trail for adversarial input patterns.
  • Exports filterAuthorizedTools from module.exports for direct unit testing without handler overhead.

Change Type

  • Bug fix (non-breaking change which fixes an issue)

Testing

19 new tests in api/server/controllers/agents/filterAuthorizedTools.spec.js using MongoMemoryServer with the real Agent model and schema — no mocks for DB logic.

Unit tests for filterAuthorizedTools (9 cases):

  • Authorized MCP tools kept, unauthorized stripped
  • System tools bypass registry entirely
  • No MCP tools in payload skips registry call
  • Registry uninitialized falls back to {}, returns only non-MCP tools, no 500
  • Mixed authorized/unauthorized/system tools produce correct filtered order
  • Empty array, null/undefined entries handled without throws
  • Correct userId forwarded to getAllServerConfigs
  • Registry called exactly once regardless of MCP tool count (lazy-fetch guard)

Handler integration tests (10 cases across 4 paths):

  • createAgentHandler: unauthorized MCP tools stripped from response and DB; no 500 on uninitialized registry; mcpServerNames persisted only for authorized servers
  • updateAgentHandler: existing MCP tools preserved when editor lacks direct access; newly added unauthorized tools stripped; newly added authorized tools accepted; registry not queried when no new MCP tools present
  • duplicateAgentHandler: unauthorized MCP tools stripped from cloned agent response and DB; mcpServerNames on duplicate contains only authorized servers
  • revertAgentVersionHandler: unauthorized tools stripped after revert, DB state confirmed via Agent.findOne; authorized tools survive revert

Test Configuration

cd api && npx jest filterAuthorizedTools.spec --testTimeout=30000

All 19 new tests pass. All 49 existing v1.spec.js tests pass.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

Copilot AI review requested due to automatic review settings March 15, 2026 21:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an authorization gap in the agent create/update/duplicate controller paths where arbitrary MCP tool identifiers could be persisted without verifying the user is allowed to reference the embedded MCP server name, potentially granting unintended MCP “consumeOnly” access via stored mcpServerNames.

Changes:

  • Introduces filterAuthorizedTools() to validate tool identifiers, including MCP tool strings against getMCPServersRegistry().getAllServerConfigs(userId).
  • Applies the filtering on agent create, update, and duplicate flows before persisting tools.
  • Adds MCP registry access to the agents controller.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread api/server/controllers/agents/v1.js Outdated
Comment thread api/server/controllers/agents/v1.js Outdated
Agent creation and update accepted arbitrary MCP tool strings without
verifying the user has access to the referenced MCP servers. This allowed
a user to embed unauthorized server names in tool identifiers (e.g.
"anything_mcp_<victimServer>"), causing mcpServerNames to be stored on
the agent and granting consumeOnly access via hasAccessViaAgent().

Adds filterAuthorizedTools() that checks MCP tool strings against the
user's accessible server configs (via getAllServerConfigs) before
persisting. Applied to create, update, and duplicate agent paths.
Addresses review findings on the MCP agent tool authorization fix:

- Wrap getMCPServersRegistry() in try/catch so uninitialized registry
  gracefully filters all MCP tools instead of causing a 500 (DoS risk)
- Guard revertAgentVersionHandler: filter unauthorized MCP tools after
  reverting to a previous version snapshot
- Preserve existing MCP tools on collaborative updates: only validate
  newly added tools, preventing silent stripping of tools the editing
  user lacks direct access to
- Add audit logging (logger.warn) when MCP tools are rejected
- Refactor to single-pass lazy-fetch (registry queried only on first
  MCP tool encountered)
- Export filterAuthorizedTools for direct unit testing
- Add 18 tests covering: authorized/unauthorized/mixed tools, registry
  unavailable fallback, create/update/duplicate/revert handler paths,
  collaborative update preservation, and mcpServerNames persistence
…ertions

- N1: Add duplicateAgentHandler integration test verifying unauthorized
  MCP tools are stripped from the cloned agent and mcpServerNames are
  correctly persisted in the database
- N2: Replace all hardcoded '_mcp_' delimiter literals with
  Constants.mcp_delimiter to prevent silent false-positive tests if
  the delimiter value ever changes
- N3: Add DB state assertion to the revert-with-strip test confirming
  persisted tools match the response after unauthorized tools are
  removed
@danny-avila danny-avila force-pushed the fix/mcp-agent-tool-authz-bypass branch from d408771 to 73b9178 Compare March 15, 2026 22:29
Reject MCP tool keys with multiple delimiters to prevent
authorization/execution mismatch when `.pop()` vs `split[1]`
extract different server names from the same key.
@danny-avila danny-avila changed the title 🛡️ fix: Validate MCP tool authorization on agent create/update 🔏 fix: Enforce MCP Server Authorization on Agent Tool Persistence Mar 15, 2026
When the MCP registry is uninitialized (e.g. server restart), existing
tools already persisted on the agent are preserved instead of silently
stripped. New MCP tools are still rejected when the registry cannot
verify them. Applies to duplicate and revert handlers via existingTools
param; update handler already preserves existing tools via its diff logic.
@danny-avila danny-avila merged commit a26eeea into dev Mar 16, 2026
9 checks passed
@danny-avila danny-avila deleted the fix/mcp-agent-tool-authz-bypass branch March 16, 2026 00:08
jcbartle pushed a commit to jcbartle/LibreChat that referenced this pull request May 11, 2026
…nny-avila#12250)

* 🛡️ fix: Validate MCP tool authorization on agent create/update

Agent creation and update accepted arbitrary MCP tool strings without
verifying the user has access to the referenced MCP servers. This allowed
a user to embed unauthorized server names in tool identifiers (e.g.
"anything_mcp_<victimServer>"), causing mcpServerNames to be stored on
the agent and granting consumeOnly access via hasAccessViaAgent().

Adds filterAuthorizedTools() that checks MCP tool strings against the
user's accessible server configs (via getAllServerConfigs) before
persisting. Applied to create, update, and duplicate agent paths.

* 🛡️ fix: Harden MCP tool authorization and add test coverage

Addresses review findings on the MCP agent tool authorization fix:

- Wrap getMCPServersRegistry() in try/catch so uninitialized registry
  gracefully filters all MCP tools instead of causing a 500 (DoS risk)
- Guard revertAgentVersionHandler: filter unauthorized MCP tools after
  reverting to a previous version snapshot
- Preserve existing MCP tools on collaborative updates: only validate
  newly added tools, preventing silent stripping of tools the editing
  user lacks direct access to
- Add audit logging (logger.warn) when MCP tools are rejected
- Refactor to single-pass lazy-fetch (registry queried only on first
  MCP tool encountered)
- Export filterAuthorizedTools for direct unit testing
- Add 18 tests covering: authorized/unauthorized/mixed tools, registry
  unavailable fallback, create/update/duplicate/revert handler paths,
  collaborative update preservation, and mcpServerNames persistence

* test: Add duplicate handler test, use Constants.mcp_delimiter, DB assertions

- N1: Add duplicateAgentHandler integration test verifying unauthorized
  MCP tools are stripped from the cloned agent and mcpServerNames are
  correctly persisted in the database
- N2: Replace all hardcoded '_mcp_' delimiter literals with
  Constants.mcp_delimiter to prevent silent false-positive tests if
  the delimiter value ever changes
- N3: Add DB state assertion to the revert-with-strip test confirming
  persisted tools match the response after unauthorized tools are
  removed

* fix: Enforce exact 2-segment format for MCP tool keys

Reject MCP tool keys with multiple delimiters to prevent
authorization/execution mismatch when `.pop()` vs `split[1]`
extract different server names from the same key.

* fix: Preserve existing MCP tools when registry is unavailable

When the MCP registry is uninitialized (e.g. server restart), existing
tools already persisted on the agent are preserved instead of silently
stripped. New MCP tools are still rejected when the registry cannot
verify them. Applies to duplicate and revert handlers via existingTools
param; update handler already preserves existing tools via its diff logic.
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.

2 participants