Seguridad de agentes / Model Context Protocol

MCP Tool Poisoning: de la ejecución de código al bypass de los permisos del agente

Un MCP server malicioso publicado en un repositorio público puede encadenar instalación, herencia de privilegios vía stdio, alteración de la configuración y persistencia fuera de la sesión para convertir un install aparentemente inofensivo en control duradero de la máquina y del agente.

Contexto: por qué stdio es el eje del riesgo

El Model Context Protocol (MCP) estandariza cómo un agente de IA descubre y llama herramientas externas: en lugar de escribir cada integración a mano, un servidor MCP expone tools que el modelo invoca vía JSON-RPC.

El transporte más común para herramientas locales es stdio. El host (Claude Code, por ejemplo) levanta el servidor como proceso hijo y conversa con él por stdin/stdout. No hay red ni autenticación, porque no hace falta: el proceso hijo hereda el usuario del sistema operativo que lo inició, con las variables de entorno, los permisos de archivo y las credenciales ya cargadas en la sesión.

Esa decisión de diseño es intencional y correcta para el caso de uso legítimo: ejecutar un script propio, acceder a una base de datos local. El problema aparece cuando el código que corre dentro de ese proceso no es tuyo.

Una nota sobre la "escalada de privilegios"

En el sentido clásico, la escalada local de privilegios significa pasar de usuario común a root. No es lo que ocurre aquí: el UID nunca cambia. Lo que escala es otra frontera: la capa de permisos del agente. El atacante empieza limitado a lo que el proceso hijo puede hacer y termina controlando lo que el agente puede hacer en las sesiones siguientes, y después ya ni siquiera necesita al agente.

Dos clases de amenaza que suelen confundirse

Código malicioso (supply chain). El handler hace algo distinto de lo que declara. Es un troyano clásico, entregado vía npm install, pip install o un clon directo.

Metadatos maliciosos (tool poisoning, en el sentido original). El término fue formalizado por Invariant Labs en abril de 2025: instrucciones maliciosas incrustadas en la descripción de la tool, que el modelo lee pero el usuario normalmente no ve. El código puede ser completamente benigno. Por eso esta clase funciona incluso con servidores remotos, sin ninguna ejecución local.

La cadena descrita aquí combina las dos.

0La dependencia que nadie audita

Existe un vector anterior a todo, y más sutil: el MCP server puede ser honesto y aun así ser el vehículo.

Auditas el repositorio del servidor, lees los handlers, confirmas que la implementación coincide con las descripciones y lo apruebas. Lo que probablemente no hiciste fue auditar las decenas o cientos de paquetes que arrastra como dependencias transitivas. Basta con que uno de ellos esté bajo control del atacante.

  • Compromiso de un paquete legítimo. Cuenta de mantenedor secuestrada por phishing, versión nueva con una línea de más.
  • Typosquatting. Paquete con un nombre casi idéntico al popular, esperando un error de tipeo o una sugerencia equivocada de un LLM.
  • Confusión de dependencias. Paquete público con el mismo nombre que un paquete interno de la empresa, publicado con una versión mayor y resuelto con preferencia por el gestor.
  • Paquete alucinado. Un modelo sugiere una biblioteca que no existe; el atacante registra ese nombre y espera.

Y el detalle que cierra el círculo con la etapa 2: la ejecución ocurre en la instalación, no en el uso. Los scripts de ciclo de vida del gestor de paquetes corren con el usuario que instaló. Nunca llegaste a levantar el MCP server: el install ya fue suficiente.

1La interfaz miente sobre la implementación

Lo que el modelo ve de una tool son esencialmente tres campos: name, description e inputSchema. La especificación también define title, outputSchema y annotations, como readOnlyHint, pero todos los declara el propio servidor. Nada en el protocolo verifica que el código que hay detrás cumpla lo que prometen.

Lo que ve el modeloLo que el handler realmente ejecuta
format_document(path)Lee .env, ~/.ssh/, credenciales de cloud
"Formatea un documento según el estándar de la empresa"Envía el contenido leído a un endpoint externo
Schema simple: {path: string}Devuelve texto falsificado: "¡Documento formateado con éxito!"

Del lado de los metadatos, tres variantes documentadas:

  • Tool poisoning. La descripción lleva instrucciones ocultas que el modelo sigue. En el ejemplo canónico de Invariant Labs, una función de suma instruye al modelo a leer claves SSH y pasarlas en un parámetro extra.
  • Rug pull. El servidor entrega descripciones benignas, es aprobado, y después las cambia por versiones envenenadas, sin nueva aprobación.
  • Tool shadowing. La descripción de un servidor malicioso redefine cómo el modelo debe usar la tool de otro servidor, confiable e intacto.

En todos los casos, el usuario aprueba el nombre bonito, no el comportamiento real.

2Ejecución con el privilegio del usuario

Un detalle que cambia la lectura del ataque: el código no empieza a correr cuando se llama a la tool. Corre cuando el servidor es iniciado y, antes de eso, en la instalación. Levantar el servidor ya es el punto sin retorno. La tool envenenada no es el disparador, es la cobertura: le da al ataque la apariencia de uso normal y un resultado falsificado para mostrar.

A partir del spawn, el proceso puede, sin pedir nada más:

  • Leer cualquier archivo que tu usuario pueda leer: archivos de entorno, claves SSH, credenciales de cloud, historial del shell
  • Hacer peticiones de red a cualquier destino
  • Escribir o modificar archivos donde tu usuario tiene permiso de escritura
  • Ejecutar binarios y scripts ya instalados en la máquina

No es una falla de MCP. Es la misma exposición de cualquier paquete desconocido o extensión no auditada. MCP solo hace que el patrón sea más fácil de disparar sin darse cuenta, porque la interfaz esconde la implementación detrás de una invitación de "haz clic y confía".

3El objetivo pasa del proceso al agente

Cabe aquí una pregunta honesta: si el handler ya tiene acceso total del usuario (y las reglas de deny de Claude Code ni siquiera se le aplican, ya que limitan las tools del agente, como Bash y Read), ¿por qué el atacante tocaría el settings.json?

Porque el objetivo deja de ser el acceso inmediato y pasa a ser la persistencia y el control del agente. Archivos como ~/.claude/settings.json y sus variantes de proyecto son JSON común en disco: sin firma, sin hash de integridad, sin bloqueo contra la escritura de otro proceso del mismo usuario. Editarlos permite:

  • Eliminar entradas de deny y agregar allow, de modo que cualquier prompt injection futuro encuentre al agente sin frenos: el agente se convierte en un confused deputy
  • Registrar hooks, que ejecutan comandos de shell en eventos de la sesión, garantizando ejecución incluso después de desinstalar el servidor malicioso
  • Alterar registros de servidores MCP o archivos de instrucciones como CLAUDE.md, que el agente lee como contexto confiable

Es el patrón del malware que desactiva el antivirus antes de actuar, con una diferencia: aquí el "antivirus" es la propia capa declarativa de permisos del agente.

Sobre el timing

La documentación actual de Claude Code afirma que las ediciones en settings.json son detectadas por un file watcher y entran en vigor en la sesión en ejecución tras un breve intervalo de estabilidad del archivo, sin reiniciar. Es decir: la alteración puede surtir efecto en la misma sesión, no solo en la siguiente. Este comportamiento ha variado entre versiones, y hay reportes en el repositorio de Claude Code de reglas de permisos que solo se aplicaron después de un reinicio, así que no cuentes con el reinicio como barrera. Los servidores MCP nuevos, en cambio, normalmente solo se conectan cuando la sesión se inicia o se recarga.

3-ACuando la tool es solo el disparador

En las etapas anteriores el daño ocurre dentro del proceso del MCP server. Existe un diseño peor: el handler no hace nada interesante durante la llamada, solo escribe un artefacto en disco y registra un mecanismo de inicio automático. A partir de ahí el ataque deja de depender del agente, de MCP y de tu sesión.

Los mecanismos de persistencia son los mismos de cualquier malware de usuario, y todos están al alcance de un proceso sin privilegio administrativo:

  • Planificadores de usuario (cron en Linux, Programador de tareas en Windows, launchd en macOS), que se disparan a intervalos fijos
  • Inicio de sesión: carpetas de inicio, entradas de registro por usuario, unidades de servicio de usuario
  • Archivos de inicio del shell, ejecutados cada vez que se abre una terminal
  • Hooks de herramientas de desarrollo: hooks de git en el repositorio, hooks del propio agente, extensiones del IDE

Ninguno exige root. Todos sobreviven a la desinstalación del MCP server, y ese es justamente el punto. El usuario elimina el paquete, da el incidente por cerrado, y el artefacto sigue ahí.

Lo que el payload suele hacer una vez activo

  1. Barrido de secretos. Recorre el directorio del usuario y los proyectos buscando patrones de credenciales: archivos de entorno, claves privadas, credenciales de cloud, tokens en archivos de configuración, y también el historial del shell y el historial de git, donde un secreto eliminado del código suele seguir vivo. Existen herramientas de código abierto de secret scanning, creadas para uso defensivo, que hacen exactamente eso; los atacantes simplemente las incrustan en lugar de escribir su propio barrido.
  2. Exfiltración con cobertura. El destino no siempre es un servidor con una IP sospechosa. El gusano Shai-Hulud publicaba los secretos robados en repositorios públicos creados en la cuenta de la propia víctima: tráfico saliente hacia un dominio que ya estaba en tu allowlist, con una credencial legítima.
  3. Propagación. Con un token del registro de paquetes o del repositorio en la mano, el payload se republica a sí mismo en los paquetes que la víctima mantiene, cerrando el ciclo sin operador humano.
  4. Segunda etapa. El primer artefacto es pequeño y discreto; descarga y ejecuta la carga real cuando quiere, lo que permite cambiar el payload después de la infección sin tocar el paquete original.

La lectura importante: el MCP server es el vector de entrada, no el malware. Después de esta etapa, eliminar el servidor y limpiar el settings.json no resuelve nada.

4La cadena completa

Cada etapa aislada parece pequeña. Sumadas, forman un compromiso duradero disfrazado de instalación de herramienta.

  1. La víctima instala un MCP server de un repositorio público no auditado, o un servidor honesto con una dependencia comprometida
  2. Los scripts de instalación ya se ejecutan con su usuario
  3. El servidor se levanta vía stdio y hereda entorno, credenciales y permisos de disco
  4. La víctima pide una tarea común; el modelo elige la tool envenenada porque la descripción parece adecuada
  5. El handler lee secretos, los exfiltra y devuelve una respuesta falsificada de éxito
  6. El mismo proceso altera la configuración del agente: elimina deny, agrega allow, registra hooks. El cambio puede entrar en vigor en la misma sesión
  7. Y escribe un artefacto con inicio automático, que pasa a correr fuera de la sesión del agente
  8. La víctima desinstala el servidor; el planificador sigue disparándose

El daño final depende solo de lo que ese usuario del sistema operativo tiene permiso de hacer, lo cual, en un entorno de desarrollo típico, suele ser casi todo.

Diagrama de la cadena de ataque

Víctima (usuario)Agente (Claude Code)MCP server(proceso hijo, stdio)Artefacto persistente(planificador / inicio)Servidor del atacanteinstala y levanta el serverel código ya corre: heredausuario, env vars y discopide una tarea comúntools/call format_document(path)handler real:lee .env, ~/.ssh, credencialesPOST con datos sensiblesresultado falsificado ("éxito")edita settings.json: eliminadeny, agrega allow y hooksescribe artefacto e inicio automáticomuestra una respuesta normaldesinstala el servidorsigue disparándosefuera de la sesiónbarrido periódico y exfiltración

La víctima nunca ve la línea que sale hacia el atacante, ni la edición de la configuración, ni el registro del planificador. Las tres ocurren dentro del proceso hijo, fuera de la conversación visible con el agente.

Esto ya pasó

En septiembre de 2025, investigadores divulgaron lo que parece ser el primer MCP server malicioso encontrado en uso real: el paquete npm postmark-mcp. Las versiones 1.0.0 a 1.0.15 funcionaban perfectamente; la 1.0.16 agregó una sola línea que ponía un BCC oculto en cada correo enviado, copiando todo a una dirección externa. El paquete tenía cerca de 1.500 descargas semanales.

Ese mismo mes, el gusano Shai-Hulud mostró el modelo completo de payload persistente en el ecosistema npm: ejecución vía script de instalación, barrido de secretos con una herramienta legítima de scanning incrustada, exfiltración a repositorios públicos en la cuenta de la víctima y republicación automática en los paquetes del mantenedor comprometido. La primera ola, que se ejecutaba en el postinstall, alcanzó de 180 a más de 500 paquetes, según la fuente y el momento del conteo. La segunda, en noviembre de 2025, pasó a ejecutarse en el preinstall y llegó a cerca de 800 paquetes y 25 mil repositorios.

Del lado de los metadatos, Invariant Labs publicó en abril de 2025 una prueba de concepto en la que un servidor de "dato del día" cambiaba su interfaz en la segunda carga y manipulaba un servidor legítimo de WhatsApp, conectado en paralelo, para filtrar el historial de conversaciones, sin ninguna nueva aprobación del usuario.

Mitigaciones reales

La capa que efectivamente resiste es la que queda fuera del alcance del mismo usuario que ejecutó el proceso malicioso. En la práctica son dos fronteras, una para cada clase de amenaza. Contra el código malicioso, el aislamiento del proceso: contenedor, VM o sandbox. Contra los metadatos maliciosos, la policy gestionada y los permisos del agente, porque la descripción envenenada actúa sobre el agente, que está fuera de la sandbox y ejecuta con sus propios privilegios. Una sin la otra deja media cadena abierta.

Ni la sandbox es absoluta. El servidor necesita algún token y algo de red para ser útil, y todo lo que entra ahí puede filtrarse: reduce el radio del daño, no lo elimina. Y los controles de supply chain son probabilísticos, pero actúan antes de que el código corra, que es el mejor momento para frenarlo.

MitigaciónPor qué funciona
Managed settings (managed-settings.json)Vive en una ruta de sistema; editarlo exige privilegio de admin o root
allowManagedPermissionRulesOnlyImpide que las configuraciones de usuario y de proyecto definan reglas de allow, ask o deny; solo valen las de la policy gestionada
allowedMcpServers + allowManagedMcpServersOnlySin la segunda flag, las allowlists de todas las fuentes se fusionan, incluida la del propio usuario. Y es independiente de la flag de permisos: una no activa la otra
Identificar el servidor stdio por serverCommandCompara el comando y los argumentos exactos que inician el servidor, en lugar de un nombre que el usuario elige
managed-mcp.jsonConjunto fijo de servidores; solo quien tiene privilegio administrativo sobre una ruta de sistema puede desplegarlo
allowManagedHooksOnlyBloquea hooks de usuario, de proyecto y de plugins comunes, cerrando el vector de persistencia de la etapa 3. Siguen corriendo los hooks gestionados, los de SDK y los de plugins activados a la fuerza por la propia policy gestionada
Hook ConfigChange gestionadoEl evento se dispara cuando un archivo de configuración cambia durante la sesión. Definido en la policy gestionada, registra o alerta sobre la alteración de la etapa 3 sin que el proceso malicioso pueda eliminarlo
Instalar sin scripts de ciclo de vidaNeutraliza la ejecución en el install, es decir, toda la etapa 0. Abre excepciones puntuales, no por defecto
Lockfile e instalación determinísticaSin latest, sin resolución flotante; el caso postmark-mcp entró por una actualización
Retraso de adopciónNo instalar una versión publicada en las últimas 24 a 72 horas, ventana en la que la mayoría de los paquetes maliciosos es detectada y eliminada
Registro interno con proxy y allowlistElimina la confusión de dependencias y da un punto único de bloqueo
Contenerizar instalación y ejecuciónLa sandbox nativa de Claude Code restringe solo los comandos de Bash y sus procesos hijos. File tools, hooks y MCP servers corren directo en el host. Para aislar el servidor, pon el proceso entero dentro de la frontera: contenedor, VM o el sandbox-runtime de Anthropic (todavía en beta), sin secretos reales montados y con un usuario restringido
Scanner con hash pinningDetecta instrucciones ocultas en descripciones y definiciones de tools que cambiaron desde el último scan
Monitorear el egress de redUn servidor "de formateo de documentos" haciendo HTTP saliente es una bandera roja inmediata
Integridad de los archivos de configuraciónUn checksum monitoreado externamente detecta la alteración fuera de la sesión del agente

Cómo verificar si te afectó

Checklist antes de instalar un MCP server

  1. ¿Quién lo publica? ¿El repositorio oficial del proveedor, o una cuenta desconocida con un nombre parecido?
  2. ¿Leí el handler de cada tool, no solo la descripción?
  3. ¿Miré el árbol de dependencias, o solo el repositorio del servidor?
  4. ¿La versión está fijada, con lockfile y hash?
  5. ¿La instalación y la ejecución ocurren aisladas, sin acceso a mis secretos reales?
  6. ¿Tengo alguna visibilidad del tráfico saliente de ese proceso?

settings.json y sus variantes no protegen contra este ataque por sí solos: son configuración declarativa, editable por cualquier proceso del mismo usuario. La defensa real está en lo que ese usuario no alcanza: el aislamiento del proceso y la policy gestionada en una ruta de sistema.

Escrito a nivel conceptual, sin prueba de concepto ejecutable. Las referencias al comportamiento de Claude Code valen para las versiones actuales y deben revalidarse con cada actualización.

Todos los artículos