Skip to content

[security] fix(multi-user): deny MCP tools until explicitly granted#579

Merged
pancacake merged 1 commit into
HKUDS:devfrom
Hinotoi-agent:fix/mcp-tool-grants-deny-default
Jun 21, 2026
Merged

[security] fix(multi-user): deny MCP tools until explicitly granted#579
pancacake merged 1 commit into
HKUDS:devfrom
Hinotoi-agent:fix/mcp-tool-grants-deny-default

Conversation

@Hinotoi-agent

Copy link
Copy Markdown
Contributor

Summary

This PR hardens the multi-user MCP tool boundary so configured MCP tools are not exposed to real non-admin users unless an administrator explicitly grants those tool names.

  • Denies real non-admin MCP tool access by default when mcp_tools is absent from the user's grant.
  • Keeps built-in optional tool semantics unchanged: enabled_tools: null still follows the default tool pool.
  • Preserves administrator access and partner/synthetic-turn metadata whitelists so existing owner-scoped partner configurations continue to work.
  • Updates grant-resolution tests to cover deny-by-default MCP behavior and explicit MCP grants.

Security issues covered

Issue Impact Severity
Non-admin chat turns could discover and load configured MCP host tools without an explicit MCP grant If a deployment configures sensitive MCP servers, a prompt-injected or low-privilege chat turn could reach backend MCP capabilities such as filesystem, shell, browser, or internal-service actions depending on the server High

Before this PR

  • A real non-admin user whose grant omitted mcp_tools resolved to unrestricted MCP access.
  • The chat pipeline could list deferred MCP tools in the extended-tool manifest and let the model call load_tools for those names.
  • Built-in optional tools and MCP tools both treated a missing grant as unrestricted, even though MCP tools can proxy backend host capabilities.
  • Existing tests did not distinguish built-in optional-tool defaults from MCP host-capability defaults.

After this PR

  • Real non-admin users with no explicit mcp_tools grant receive an empty MCP allowlist.
  • Admin users remain unrestricted.
  • Partner/synthetic turns continue to use their explicit metadata/owner-scoped MCP filter as the authority.
  • Tests now lock in the split semantics: built-ins keep their default behavior, while MCP tools fail closed unless granted.

Why this matters

MCP tools execute on the backend side through configured MCP servers. In deployments that enable powerful MCP servers, a model-selected load_tools call should not be treated as the user's authorization to invoke host-capability tools. This patch makes the server-side grant resolver enforce an explicit MCP permission boundary for real non-admin users.

How this differs from related issue/PR

This is in the same general trust-boundary family as the older TutorBot shell/filesystem reports, but it covers the newer deferred MCP tool path rather than those legacy TutorBot tool implementations.

The relevant boundary here is:

  • grant resolution for real non-admin users,
  • deferred MCP tool visibility/loading through the chat pipeline, and
  • preservation of partner/synthetic metadata-scoped allowlists.

Attack flow

low-privilege chat user or untrusted prompt-injected content
    -> chat agent sees configured MCP tools in the deferred-tool manifest
        -> model calls load_tools for a sensitive MCP tool
            -> loaded MCP schema becomes callable in the turn
                -> backend MCP server executes the requested host-side tool

Affected code

Issue Files
Non-admin MCP tools were unrestricted when the grant omitted mcp_tools deeptutor/multi_user/tool_access.py, deeptutor/multi_user/grants.py, deeptutor/agents/chat/agentic_pipeline.py, tests/multi_user/test_tool_access.py

Root cause

Issue 1: Missing deny-by-default MCP grant semantics

  • The grant resolver used None as unrestricted for both ordinary optional tools and MCP tools.
  • MCP tools are materially different because they can represent backend host capabilities configured by the deployment.
  • The chat deferred-tool path intersected caller filters with the current user's grant, but an absent real-user MCP grant still resolved to unrestricted.

CVSS assessment

Issue CVSS v3.1 Vector
Non-admin chat turns could load configured MCP host tools without an explicit grant 8.1 High CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N

Rationale:

  • The attacker is assumed to be a low-privilege user who can start a chat turn, or content that can influence such a turn through prompt injection.
  • Impact depends on the configured MCP server. Sensitive MCP tools can expose host filesystem, command, browser, or internal-service capabilities.
  • The issue does not require administrator privileges once a sensitive MCP server is configured and visible to the chat runtime.

Safe reproduction steps

  1. Configure a sensitive MCP tool in a deployment where a real non-admin user has no explicit mcp_tools grant.
  2. Start a chat turn as that non-admin user.
  3. Observe that, before this patch, the missing grant resolves as unrestricted and the deferred MCP tool can be listed/loaded.
  4. After this patch, run the grant-resolution regression tests and observe that missing non-admin MCP grants resolve to an empty set while explicit grants return only the named tools.

Expected vulnerable behavior

  • A missing real-user mcp_tools grant should not expose deployment-wide MCP tools to non-admin users.
  • Before this patch, a missing MCP grant resolved as unrestricted.
  • The safe proof signal is the grant resolver returning set() for a non-admin user without mcp_tools, while still returning the explicit allowlist when an admin grants tool names.

Changes in this PR

  • Updates allowed_mcp_tools() so real non-admin users fail closed when mcp_tools is absent.
  • Clarifies grant comments so None is not read as universally unrestricted across built-in tools and MCP tools.
  • Updates deferred-tool preparation to preserve partner/synthetic metadata whitelist authority while applying real-user grants to real users.
  • Updates multi-user grant tests for the new MCP-specific default and explicit allowlist behavior.

Files changed

Category Files What changed
Runtime grant resolution deeptutor/multi_user/tool_access.py Real non-admin missing MCP grant now resolves to set() instead of unrestricted; docs clarify split semantics
Grant model comments deeptutor/multi_user/grants.py Documents that MCP tools deny by default for non-admin runtime access until explicitly granted
Chat deferred-tool pipeline deeptutor/agents/chat/agentic_pipeline.py Intersects caller MCP filters with real-user grants while preserving partner/synthetic metadata whitelist behavior
Regression tests tests/multi_user/test_tool_access.py Covers non-admin missing-grant denial and explicit MCP grant allowlists

Maintainer impact

  • The patch is intentionally scoped to MCP/deferred-tool authorization semantics.
  • Built-in optional tools, model grants, knowledge-base grants, skill grants, and exec policy behavior are unchanged.
  • Existing admin access remains unrestricted.
  • Existing partner/synthetic configurations keep using their explicit metadata whitelist model.
  • The compatibility change is that real non-admin users now need an explicit mcp_tools grant before MCP tools are listed or loadable.

Fix rationale

MCP tools are deployment-defined backend capabilities, not ordinary UI-toggleable helper tools. A missing grant should therefore be interpreted conservatively for real non-admin users. Keeping administrator access unrestricted and preserving explicit partner metadata whitelists keeps the change narrow while closing the host-capability boundary for ordinary users.

Type of change

  • Security fix
  • Tests
  • Documentation update
  • Refactor with no behavior change

Test plan

  • python3 -m pytest -q tests/multi_user/test_tool_access.py tests/services/mcp/test_mcp_config.py
  • python3 -m compileall -q deeptutor/multi_user deeptutor/agents/chat tests/multi_user/test_tool_access.py
  • git diff --check
  • docker run --rm -v "$PWD":/work -w /work python:3.11 bash -lc 'python -m pip install -q --upgrade pip && python -m pip install -q -e .[dev] && python -m pytest -q tests/multi_user/test_tool_access.py tests/services/mcp/test_mcp_config.py'

Results:

  • Host focused pytest: 22 passed
  • Docker focused pytest: 22 passed

Disclosure notes

  • This PR is bounded to the real non-admin MCP/deferred-tool grant boundary.
  • Impact depends on a deployment configuring sensitive MCP servers.
  • No production systems, real secrets, or destructive payloads were used for validation.
  • No unrelated files are changed.

@pancacake

Copy link
Copy Markdown
Collaborator

Thanks for your contribution!

pancacake added a commit that referenced this pull request Jun 21, 2026
…grant UI

Follow-up refinements on top of #579 (deny MCP tools until explicitly
granted), tightening decoupling and front/back consistency:

- Decouple: extract the duplicated `metadata.source == "partner"` check
  (deferred-tools, exec gate, tool composition, cron owner routing) into a
  single `AgenticChatPipeline._is_partner_turn` helper, so the partner
  authority-via-metadata contract has one definition instead of four
  inline copies.
- Frontend parity: the backend now denies MCP for non-admin users when the
  grant omits `mcp_tools`, but GrantEditor still showed `null` as
  "Default · all" / "all MCP". Make ModeSwitch's default-mode label
  caller-supplied, label the MCP section "Default · none" / "no MCP", and
  add a one-line rationale so admins are not misled into granting nothing.

Built-in optional tools keep `null = all`; only MCP tools fail closed.

Tests: ruff clean; pytest multi_user/mcp/partners/chat (545 passed);
web tsc + eslint clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@pancacake
pancacake merged commit 9004637 into HKUDS:dev Jun 21, 2026
vaskoyudha added a commit to vaskoyudha/deeptutor-for-programmer-fork that referenced this pull request Jul 25, 2026
vaskoyudha added a commit to vaskoyudha/deeptutor-for-programmer-fork that referenced this pull request Jul 25, 2026
…grant UI

Follow-up refinements on top of HKUDS#579 (deny MCP tools until explicitly
granted), tightening decoupling and front/back consistency:

- Decouple: extract the duplicated `metadata.source == "partner"` check
  (deferred-tools, exec gate, tool composition, cron owner routing) into a
  single `AgenticChatPipeline._is_partner_turn` helper, so the partner
  authority-via-metadata contract has one definition instead of four
  inline copies.
- Frontend parity: the backend now denies MCP for non-admin users when the
  grant omits `mcp_tools`, but GrantEditor still showed `null` as
  "Default · all" / "all MCP". Make ModeSwitch's default-mode label
  caller-supplied, label the MCP section "Default · none" / "no MCP", and
  add a one-line rationale so admins are not misled into granting nothing.

Built-in optional tools keep `null = all`; only MCP tools fail closed.

Tests: ruff clean; pytest multi_user/mcp/partners/chat (545 passed);
web tsc + eslint clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
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