fix(security): stop the agent from re-enabling tools an admin disabled (#6357)

* fix(security): stop the agent from re-enabling tools an admin disabled

settings.json:disabled_tools is the only global tool-permission boundary
the Settings > Agent Tools panel offers, yet manage_settings exposed an
`enable_tool` action that removed entries from it. Any agent turn could
undo the admin's restriction, and because the agent reads untrusted web
pages, emails and documents, a planted instruction could re-enable bash,
email or any other tool that had been switched off.

The denylist can now only be narrowed from a turn: `disable_tool` and
`list_tools` keep working, `enable_tool` is refused with a message that
points the user to Settings > Agent Tools, and the action is dropped from
the function schema, the prompt rule and the tool index so models are not
steered into calling it. The free-form set/delete/reset actions were
already unable to reach `disabled_tools`; a test now pins that.

Fixes #5523

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UAB5sz5srMKR7rbeDFtdMH

* fix(settings): close internal API tool-policy bypass

Refuse global tool-policy replacement from internal-token requests even when an admin owner is impersonated, and block the same write in app_api. Route explicit image re-enable requests to list_tools with a Settings pointer. Keep disabled_tools outside general settings actions even if a default is added later. Remove the retired action from capability metadata. Validation: 573 focused tests passed; the running app rejected handler, generic API and encoded-path internal HTTP attempts while preserving human administrator controls.

* fix(mcp): block agent writes to per-server MCP tool policy

PATCH /api/mcp/servers/{id}/tools only checked require_admin, which accepts
the internal tool token, and app_api still forwarded it, so an agent turn
could re-enable MCP tools an admin had switched off. Refuse the internal
token on that route (same check as POST /api/tools) and add PATCH
/api/mcp/servers to the app_api blocklist, with regression tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BCbhLNC7mJsunxqfwwshqn

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Afonso Junior <chatgpt@yack.com.br>
This commit is contained in:
Afonso Coutinho
2026-10-27 22:50:35 -06:00
committed by GitHub
co-authored by Claude Opus 5.5 Afonso Junior
parent a646b59110
commit 57a1859f08
11 changed files with 295 additions and 25 deletions
+7 -1
View File
@@ -5,6 +5,7 @@ import os
import uuid
import urllib.parse
import html
import secrets
from pathlib import Path
from fastapi import APIRouter, Form, HTTPException, Request
from fastapi.responses import RedirectResponse, HTMLResponse
@@ -12,7 +13,7 @@ import logging
import httpx
from core.database import McpServer, SessionLocal
from core.middleware import require_admin
from core.middleware import INTERNAL_TOOL_HEADER, INTERNAL_TOOL_TOKEN, require_admin
from src.constants import DATA_DIR, MCP_OAUTH_DIR
from src.mcp_manager import McpManager
@@ -424,6 +425,11 @@ def setup_mcp_routes(mcp_manager: McpManager):
Expects JSON body: {"disabled": ["tool_name_1", "tool_name_2"]}
"""
require_admin(request)
internal_token = request.headers.get(INTERNAL_TOOL_HEADER)
if internal_token and secrets.compare_digest(internal_token, INTERNAL_TOOL_TOKEN):
# Same boundary as POST /api/tools: an agent turn must not be able
# to re-enable MCP tools an admin switched off.
raise HTTPException(403, "Change MCP tool policy in Settings > Integrations, not from chat")
db = SessionLocal()
try:
srv = db.query(McpServer).filter(McpServer.id == server_id).first()
+7
View File
@@ -7,6 +7,7 @@ import json
import hashlib
import ipaddress
import socket
import secrets
import time as _time
import logging
import threading
@@ -3091,6 +3092,12 @@ def setup_model_routes(model_discovery):
def update_tools(body: ToolsUpdate, request: Request):
"""Update which tools are disabled."""
require_admin(request)
from core.middleware import INTERNAL_TOOL_HEADER, INTERNAL_TOOL_TOKEN
internal_token = request.headers.get(INTERNAL_TOOL_HEADER)
if internal_token and secrets.compare_digest(internal_token, INTERNAL_TOOL_TOKEN):
# Owner impersonation can carry an admin username, so check the
# authenticated transport rather than request.state.current_user.
raise HTTPException(403, "Change tool policy in Settings > Agent Tools, not from chat")
settings = _load_settings()
settings["disabled_tools"] = body.disabled
_save_settings(settings)