s14: MCP handler defaults can be overridden by tool input, bypassing host policy

Abierto Apto para principiantes
#608 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 8 días

@shreyanshj10 ya está trabajando en esto.

Desde el 3/11/2026.

  • #613 de @shreyanshj10 — abierto

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
72/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python
Área
security

Línea de trabajo

Empieza en s14_mcp_plugin/code.py alrededor de la línea 357, donde se construyen los handlers de MCP, y lee permission_hook para ver cómo mcp_tool_policies se indexa por el nombre con prefijo. Ejecuta el repro mínimo de FakeClient del issue para confirmar que una clave 'tool' en la entrada sobrescribe el valor por defecto. Se considera terminado cuando los kwargs en tiempo de llamada ya no pueden cambiar la herramienta enrutada y una llamada a status con una clave 'tool' extra es rechazada o no se enruta al trigger.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Summary

In s14_mcp_plugin/code.py, MCP handlers are built like this (around line 357):

handlers[prefixed] = (
    lambda *, client=server, tool=raw_name, **kwargs:
    client.call_tool(tool, kwargs)
)

The default arguments fix the late-binding problem described in README §5. However, tool and client are still ordinary keyword parameters. If the model's tool input contains a "tool" key, it replaces the default. The call is then routed to a different server tool than the one that permission_hook checked.

Why it matters for this lesson

permission_hook looks up mcp_tool_policies by the prefixed name only. With the default MCP_HOST_POLICY, ("deploy", "status") is allow, and ("deploy", "trigger") is confirm. So this tool call:

{"name": "mcp__deploy__status", "input": {"service": "web", "tool": "trigger"}}

passes as status without asking the user. The deploy server then runs call_tool("trigger", {"service": "web"}) and returns [deploy] Triggered: web.

The model might send this input by mistake, or because content it read told it to. Lesson 6 says the host policy decides what runs, and this input gets around that policy. The input schema is not validated before dispatch, so the extra key is not rejected.

Minimal repro (no API key needed)

class FakeClient:
    def call_tool(self, tool, args):
        return f"server ran {tool!r} with {args}"

server = FakeClient()
handlers = {}
for raw_name in ["status", "trigger"]:
    prefixed = f"mcp__deploy__{raw_name}"
    handlers[prefixed] = (
        lambda *, client=server, tool=raw_name, **kwargs:
        client.call_tool(tool, kwargs)
    )

print(handlers["mcp__deploy__status"](**{"service": "web", "tool": "trigger"}))
# server ran 'trigger' with {'service': 'web'}

I also reproduced this with the unmodified s14_mcp_plugin/code.py. I used a scripted fake Anthropic client that returns the tool call above. The status call ran trigger without a permission prompt.

Suggested fix

Bind the values in a closure so that call-time arguments cannot reach them:

def make_handler(client, tool):
    return lambda **kwargs: client.call_tool(tool, kwargs)

handlers[prefixed] = make_handler(server, raw_name)

With this change, the same input calls status with an extra tool argument. The demo server's status function rejects that argument with MCP error: TypeError: ... unexpected keyword argument 'tool', and nothing is deployed. functools.partial would not fix this, because keyword arguments given at call time also override the keywords stored in a partial.

If you want a short line in the README, §5 could say that default arguments solve late binding but remain overridable by caller kwargs.

Thanks for the great course! I found this while building a Traditional Chinese walkthrough of it.

Lenguaje dominante
Python
Estrellas
79.5k
Forks
12.7k
Merge medio
7 d 17 h
PR fusionados (30 d)
4

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de shareAI-lab/learn-claude-code

Todos los issues de shareAI-lab/learn-claude-code

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.