Seguridad en MCP en producción
Qué exige ya la especificación MCP, qué rompieron de verdad los CVE de agentes de código de 2026 y los controles que aguantan: validación de audiencia, fijado de herramientas, aislamiento, política de salida y auditoría.
- MCP
- Seguridad
- IAM
- Agentes de IA
- OAuth
Un servidor MCP no es una API más. Es un canal de instrucciones que el modelo lee y ejecuta. Cada nombre de herramienta, cada descripción de parámetro y cada cadena que devuelve una herramienta aterrizan en la misma ventana de contexto que la petición del usuario, y el modelo no tiene forma fiable de distinguir datos de órdenes. Esa única propiedad convierte la seguridad del Model Context Protocol en una disciplina distinta de la seguridad de APIs, y explica por qué los fallos de 2026 se han repetido con tanta regularidad.

Esto es una guía operativa, no un ensayo de modelado de amenazas. Recorre lo que la especificación ya exige, qué rompieron realmente los incidentes de 2026 y los controles que aguantan en producción, con los comandos para verificar cada uno.
Qué cambia MCP en tu modelo de amenazas
En una integración clásica, el desarrollador decide qué llamada se hace, con qué argumentos y en qué punto del código. Con MCP el cliente entrega al modelo las definiciones de herramientas de todos los servidores conectados, y es el modelo quien elige qué invocar y con qué parámetros. OWASP lo resume sin rodeos: MCP concentra en un mismo sitio la inyección de prompts, el riesgo de cadena de suministro y el problema del diputado confundido.[owaspcs]
- Los metadatos de herramienta se comportan casi como código. Las descripciones y los JSON Schema los lee el modelo como guía. Un campo
descriptionenvenenado se parece más a código inyectado que a documentación. - La salida de una herramienta es entrada no confiable. Una página web, el título de una incidencia o una fila de base de datos vuelven al contexto con el mismo rango que el prompt de sistema.
- El modelo ve todos los servidores a la vez. La descripción de un servidor malicioso puede alterar cómo usa el agente un servidor distinto y confiable. Eso es tool shadowing, y una revisión servidor a servidor no lo detecta.[owaspcs]
La especificación es explícita en que no resuelve esto por ti: la autorización es OPCIONAL para las implementaciones MCP; cuando se usa transporte HTTP DEBERÍA seguirse la especificación, y a las implementaciones stdio se les indica que tomen credenciales del entorno.[spec-auth] El resto —aislamiento, integridad de herramientas, control de salida— queda en manos de quien publique el host, el cliente o el servidor.
Los incidentes de 2026 que fijan el listón
Los dos casos más claros del año no salieron de laboratorios exóticos, sino de la configuración por defecto de los propios fabricantes. El 5 de agosto de 2026, en Black Hat USA, Novee Security probó los agentes de código de Anthropic, Google y OpenAI en la configuración que cada fabricante entrega por defecto. Del trabajo salieron dos CVE, ambos corregidos. Lo que permitía cada uno es muy distinto, y ahí está lo interesante.[ghsa-google][ghsa-anthropic]
| Caso | Qué falló de verdad | Corregido en |
|---|---|---|
| CVE-2026-12537 — Gemini CLI, CVSS v4 10.0 según la CNA[cve12537] | Inyección de comandos del sistema en el lanzador de contenedores, alcanzable mediante un fichero .gemini/.env manipulado. El código se ejecutaba en el host de una plataforma CI headless antes de que arrancara el sandbox. | Gemini CLI 0.39.1 · run-gemini-cli 0.1.22[ghsa-google] |
| CVE-2026-54316 — Claude Code, CVSS v4 6.0 (NVD v3.1: 9.1)[cve54316] | huggingface.co estaba preaprobado como nombre de host desnudo para WebFetch, así que cualquier ruta de ese dominio —incluidos repositorios controlados por el atacante— se autoaprobaba. HuggingFace contabiliza esas peticiones en servidor, así que los contadores de descarga se convirtieron en un canal encubierto fuera de banda para exfiltrar datos al alcance del agente: ficheros, variables de entorno, salida de comandos. Solo confidencialidad: no hay ejecución de código. Afectó a ≥ 0.2.54 y < 2.1.163. | Claude Code 2.1.163[ghsa-anthropic] |
| OpenAI Codex — sin CVE ni versión nueva | Dos pasadas de Codex compartían un mismo checkout, de modo que la primera podía escribir AGENTS.md, el fichero que la segunda cargaba como sus propias instrucciones. Se corrigió en el workflow, no en el producto. | Separación de pasadas; la guía ya trata los ficheros de instrucciones del repositorio como entrada no confiable[codex] |
El patrón conviene enunciarlo con precisión, porque generaliza mucho más allá de estos tres productos: el fallo estaba en el arnés (harness), no en el modelo. Un componente marcaba un valor como seguro y otro posterior actuaba sobre él con más autoridad. Según la información publicada el 7 de agosto de 2026, ninguno de los dos CVE figuraba en el catálogo KEV de CISA, y nada en el registro público muestra estas cadenas usadas contra un objetivo real: lo relevante es la clase de defecto, no una campaña activa.
Léelo junto al modelo operativo de DevOps vs MLOps vs LLMOps: si no puedes nombrar el paquete exacto que produjo una acción —modelo, prompt, esquema de herramienta, política—, no puedes explicar un incidente ni confiar en un rollback.
La línea base que la especificación ya exige
Buena parte del trabajo de «seguridad MCP» consiste simplemente en hacer bien OAuth. La revisión 2025-06-18 reclasificó el servidor MCP como resource server puro de OAuth 2.1, y la revisión 2025-11-25 afinó el descubrimiento y el consentimiento.[spec-log] Si operas un servidor MCP remoto, lo que sigue no son recomendaciones.[oauth21][rfc9700][nsa]
Descubrimiento: RFC 9728, no un README
Los servidores MCP DEBEN implementar OAuth 2.0 Protected Resource Metadata y anunciar al menos una entrada en authorization_servers; los clientes DEBEN usarlo para el descubrimiento. Ante una petición sin autenticar, el servidor responde 401 con la cabecera WWW-Authenticate apuntando al documento de metadatos.[spec-auth][rfc9728]
curl -sSI https://mcp.example.com/mcp | grep -i '^www-authenticate'
# www-authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
curl -sS https://mcp.example.com/.well-known/oauth-protected-resource \
| jq '{resource, authorization_servers, scopes_supported}'
curl -sS https://auth.example.com/.well-known/oauth-authorization-server \
| jq '{issuer, token_endpoint, code_challenge_methods_supported}'Vinculación de audiencia: el control que corta la reutilización de tokens
Los clientes DEBEN enviar el parámetro resource de RFC 8707 tanto en la petición de autorización como en la de token, con la URI canónica del servidor destino, y DEBEN enviarlo aunque el servidor de autorización no lo soporte.[rfc8707] El servidor DEBE validar que el token presentado se emitió para él y rechazarlo en caso contrario.[spec-auth]
El token passthrough —aceptar un token que no se emitió para ti y reenviarlo aguas abajo— está expresamente prohibido. El razonamiento de la especificación es operativo: destruye la trazabilidad de auditoría, evita los límites de tasa y las validaciones que dependen de la audiencia, y convierte tu servidor en un proxy para cualquiera que tenga un token robado.[spec-sec] Si tu servidor MCP llama a una API superior, actúa como cliente de esa API y necesita su propio token.
CANONICAL = "https://mcp.example.com/mcp" # RFC 8707 resource identifier
def authorize(token: dict) -> None:
aud = token.get("aud")
aud = aud if isinstance(aud, list) else [aud]
if CANONICAL not in aud:
raise Unauthorized("token audience does not include this server") # 401
if not required_scopes(token).issuperset(scopes_for_current_tool()):
raise Forbidden("insufficient scope") # 403
# Never forward `token` upstream. Mint a separate one for the upstream API.Registro de clientes: CIMD pasa a ser el valor recomendado
La revisión 2025-11-25 incorporó los OAuth Client ID Metadata Documents como mecanismo recomendado de registro de clientes (SEP-991), junto con soporte de OpenID Connect Discovery y consentimiento incremental de scopes vía WWW-Authenticate.[spec-log][cimd] Con CIMD el client_id es una URL HTTPS que resuelve a un pequeño documento JSON que describe al cliente; el servidor de autorización lo descarga, valida los redirect URI registrados y aplica su política. Sin coordinación previa y sin la acumulación de registros desechables que genera el Dynamic Client Registration.
Importa porque el DCR es uno de los ingredientes del ataque de diputado confundido. Cuando un servidor MCP que hace de proxy usa un client ID estático contra un servidor de autorización de terceros, además permite registro dinámico, y además el tercero deja una cookie de consentimiento, un atacante puede registrar un cliente con su propio redirect_uri, reutilizar la cookie de la víctima para saltarse la pantalla de consentimiento y recoger el código de autorización. La mitigación es consentimiento por cliente almacenado en servidor y comprobado antes del flujo con el tercero, validación exacta del redirect URI y state de un solo uso.[spec-sec]
Nada de esto es ingeniería de identidad nueva: es la misma disciplina de diseñar un IAM que escala y los mismos compromisos que aparecen al elegir entre Keycloak y Entra ID. Reutiliza el servidor de autorización que ya operas y auditas; no dejes que cada servidor MCP se monte el suyo.
Diez modos de fallo y el control que cierra cada uno
El OWASP MCP Top 10 es un documento vivo en beta (v0.1), con próxima versión prevista para octubre de 2026, así que trátalo como lista de verificación y no como objetivo de certificación.[owasp10]
| Riesgo OWASP MCP | Control que lo cierra |
|---|---|
| MCP01 Mala gestión de tokens y exposición de secretos | Tokens de vida corta, por servidor y por usuario, en el almacén de credenciales del sistema operativo; nunca en ficheros de configuración. Rotación de refresh tokens para clientes públicos.[spec-auth] |
| MCP02 Escalada de privilegios por crecimiento de scopes | Empezar con el conjunto mínimo de scopes y elevar de forma incremental mediante retos WWW-Authenticate scope="…". Ni *, ni scopes globales, ni publicar el catálogo completo en scopes_supported.[spec-sec] |
| MCP03 Envenenamiento de herramientas (rug pull, esquema, shadowing) | Fijar un SHA-256 sobre el JSON canónico de nombre + descripción + esquema de entrada en el momento de la aprobación; recalcularlo antes de cada ejecución y fallar cerrado ante cualquier desviación.[owaspcs] |
| MCP04 Cadena de suministro y manipulación de dependencias | Fijar versiones y digests, verificar checksums o firmas, escanear dependencias y revisar typosquatting antes de instalar.[owaspcs] |
| MCP05 Inyección y ejecución de comandos | Nada de interpolar en shell cadenas generadas por el modelo: solo arrays de argumentos, JSON Schema estricto con additionalProperties: false y restricciones pattern. |
| MCP06 Subversión del flujo de intención / inyección contextual | Tratar toda respuesta de herramienta como dato. Eliminar marcado con aspecto de instrucción, preferir extracción estructurada al HTML crudo y alertar ante patrones imperativos en la salida.[owaspcs] |
| MCP07 Autenticación y autorización insuficientes | OAuth 2.1 con PKCE, validación de audiencia y autorización en cada petición. Las sesiones NO DEBEN usarse como autenticación.[spec-sec] |
| MCP08 Falta de auditoría y telemetría | Registrar cada invocación con parámetros completos, identidad del llamante, hash de la herramienta e ID de correlación; redactar secretos; enviar al SIEM. |
| MCP09 Servidores MCP en la sombra | Registro de servidores aprobados, política de salida que bloquee el resto y escaneos periódicos de portátiles e imágenes de CI. |
| MCP10 Inyección de contexto y sobreexposición | Acotar el contexto por tarea y por inquilino; que los datos recuperados en una sesión no persistan en otra. |
Fortificación, capa a capa
Fija las definiciones de herramienta o asume los rug pulls
Un rug pull funciona porque el usuario aprueba una herramienta una vez y no vuelve a mirarla. El arreglo es mecánico: se calcula el hash de la definición al aprobarla y se verifica antes de cada llamada.
import hashlib, json
def tool_fingerprint(tool: dict) -> str:
canonical = json.dumps(
{"name": tool["name"],
"description": tool.get("description", ""),
"inputSchema": tool.get("inputSchema", {})},
sort_keys=True, separators=(",", ":"), ensure_ascii=False)
return hashlib.sha256(canonical.encode()).hexdigest()
if tool_fingerprint(live_tool) != PINNED[server_id][live_tool["name"]]:
raise ToolDefinitionDrift(server_id, live_tool["name"]) # fail closedFalla cerrado. Una herramienta cuya descripción ha cambiado desde la aprobación es una herramienta nueva y necesita una decisión humana nueva, no un reintento silencioso.
Aísla los servidores locales de verdad
Un servidor MCP local es un binario que corre con los privilegios del cliente. La guía de la especificación para la configuración en un clic es inequívoca: mostrar el comando exacto sin truncar, avisar de que se ejecuta con los privilegios del cliente y aislarlo con acceso mínimo a sistema de ficheros y red.[spec-sec] Prefiere stdio en local, porque limita el alcance al proceso cliente. Si tienes que usar HTTP, enlaza a 127.0.0.1 —nunca a 0.0.0.0—, exige token y valida la cabecera Host en cada petición.[owaspcs]
docker run --rm -i \
--network none \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m \
--cap-drop ALL --security-opt no-new-privileges \
--pids-limit 128 --memory 512m --cpus 1 \
--user 10001:10001 \
-v "$PWD/workspace:/workspace:ro" \
ghcr.io/example/mcp-server@sha256:<digest>El digest @sha256: es el control de cadena de suministro, no una preferencia estética: una etiqueta se puede reapuntar, un digest no. Y si ya ejecutas cargas en Kubernetes, aplica el mismo criterio de radio de impacto que en cuándo no usar Kubernetes: un contenedor por servidor MCP compensa; un plano de control por servidor MCP, no.
Controla la salida: el SSRF está en el descubrimiento
Este se pasa por alto con facilidad porque ataca al cliente, no al servidor. Durante el descubrimiento OAuth el cliente descarga URLs que le da el servidor: la URL de resource_metadata de WWW-Authenticate, las entradas de authorization_servers y después los endpoints de los metadatos del AS. Un servidor malicioso puede apuntar cualquiera de ellas a http://169.254.169.254/ y leer credenciales de instancia cloud a través de tu cliente.[spec-sec]
Los clientes DEBERÍAN exigir HTTPS fuera de loopback, bloquear rangos privados y link-local (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16, fc00::/7, fe80::/10), aplicar las mismas reglas a cada salto de redirección y enrutar los despliegues de servidor a través de un proxy de salida. La especificación advierte explícitamente contra implementar la validación de IP a mano —las codificaciones octal, hexadecimal e IPv4 mapeada en IPv6 derrotan a casi cualquier parser casero— y contra el DNS rebinding.[spec-sec][owaspssrf]
Agentes de código en CI: el objetivo más valioso que tienes
Un agente en CI tiene escritura sobre el repositorio y acceso a los secretos del workflow, y su entrada es lo que un desconocido escribió en una incidencia. Esa es exactamente la configuración que atacó el trabajo de Black Hat.
on:
issues:
types: [opened]
permissions:
contents: read
issues: read
id-token: none
jobs:
triage:
runs-on: ubuntu-latest
environment: agent-sandbox
timeout-minutes: 10
steps:
- uses: actions/checkout@<commit-sha>
with: { persist-credentials: false }
- run: printf '%s' "$ISSUE_BODY" > /tmp/untrusted.md
env:
ISSUE_BODY: ${{ github.event.issue.body }}
- run: agent-cli review --input /tmp/untrusted.md --sandbox read-only --no-network- Nunca interpoles
${{ github.event.* }}dentro de una cadenarun:. Pásalo por variable de entorno o por fichero. - Los ficheros de instrucciones del repositorio son entrada no confiable. Lo dice ya la propia guía de OpenAI, después de que dos pasadas de Codex compartiendo checkout permitieran que la primera escribiera las instrucciones de la segunda.[codex]
- Ejecuta el agente al final del job. La misma guía advierte de que, si no, puede dejar ficheros para pasos privilegiados posteriores.[codex]
- Separa las pasadas en jobs distintos con checkouts distintos, para que una no pueda escribir la entrada de la siguiente.
- Parchea el arnés. Gemini CLI 0.39.1, run-gemini-cli 0.1.22 y Claude Code 2.1.163; después audita todo workflow que pueda disparar alguien de fuera.
Auditoría: que toda llamada sea reconstruible
OWASP recoge la falta de telemetría como riesgo propio, y es el que convierte un incidente acotado en uno sin límites.[owasp10] Registro mínimo por invocación: identidad autenticada del llamante, servidor y herramienta, huella de herramienta verificada, parámetros completos con secretos redactados, camino de decisión (autoaprobado, permitido por política, aprobado por persona), resultado, latencia e ID de correlación que enlace toda la ejecución del agente. Alerta ante herramientas vistas por primera vez, elevaciones de scope y patrones con forma de instrucción en la salida.
Dos decisiones estructurales lo hacen manejable más allá de un puñado de servidores. Primera: pon una pasarela delante; un único punto que aplique listas de permitidos, aislamiento por servidor, política de salida y registro vale más que la misma política reimplementada en cada host. Segunda: quédate con la respuesta aburrida siempre que puedas — el argumento de las arquitecturas cloud aburridas encaja aquí literalmente: menos piezas móviles, menos fronteras de confianza, menos sitios donde esconder un diputado confundido. La misma contención compensa al montar la pila de agentes.
Una pasada de verificación para hoy mismo
Nueve comprobaciones. Si alguna falla, has encontrado trabajo real: 1) petición sin autenticar rechazada con puntero utilizable; 2) metadatos de recurso protegido publicados; 3) token emitido para otra audiencia rechazado; 4) HTTP plano no aceptado; 5) servidores locales sin escuchar en todas las interfaces; 6) definiciones de herramienta idénticas a las aprobadas; 7) imágenes fijadas por digest; 8) sin datos de evento interpolados en pasos de shell; 9) versiones del arnés por encima de las corregidas.
# 1
curl -sS -o /dev/null -w '%{http_code}\n' https://mcp.example.com/mcp
# 2
curl -sSf https://mcp.example.com/.well-known/oauth-protected-resource | jq -e '.authorization_servers[0]'
# 3
curl -sS -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN_FOR_OTHER_AUDIENCE" \
https://mcp.example.com/mcp
# 4
curl -sS -o /dev/null -w '%{http_code}\n' http://mcp.example.com/mcp
# 5
ss -ltnp | grep -E '0\.0\.0\.0|\[::\]'
# 6
mcp-inspect list-tools --server internal | sha256sum
# 7
grep -rEn 'image:\s*\S+:(latest|main|v?[0-9]+)\s*$' deploy/
# 8
grep -rn 'github\.event\.\(issue\|comment\|pull_request\)' .github/workflows/ | grep 'run:'
# 9
gemini --version; claude --versionLa comprobación 3 es la que más veces falla a la primera. Es también sobre la que la especificación es menos ambigua.
Qué hacer esta semana
MCP merece la pena. El protocolo elimina una clase real de fricción de integración y la revisión 2025-11-25 movió decisiones de seguridad importantes desde el folclore hasta la especificación. Pero se entrega como capacidad, no como plano de control. Los controles que importan son los que ya conoces de identidad y cadena de suministro, aplicados a una frontera que la mayoría de equipos todavía no ha dibujado.
Por orden de prioridad: parchea los arneses; haz obligatoria la validación de audiencia; fija definiciones de herramienta y digests de imagen; aísla los servidores locales y cierra la salida; y pon una puerta humana delante de todo lo destructivo. Las organizaciones que se quemen en los próximos doce meses no serán las que adoptaron MCP, sino las que lo adoptaron y dejaron el arnés por defecto.
Preguntas frecuentes
¿MCP es seguro por defecto?
No, y la especificación no lo afirma. La autorización es opcional en las implementaciones MCP, y el aislamiento, la integridad de herramientas y la política de red quedan en manos de quien construye el host, el cliente o el servidor. Un servidor MCP local se ejecuta con los mismos privilegios que el cliente que lo lanzó.
¿Qué es el envenenamiento de herramientas en MCP?
Es manipular las herramientas de las que depende un agente para que el modelo se comporte de otra forma. OWASP agrupa tres subtécnicas: el rug pull, cuando se cambia la descripción de una herramienta ya aprobada; el envenenamiento de esquema, cuando la propia definición de la interfaz induce a error; y el tool shadowing, cuando la descripción de un servidor malicioso altera el uso de herramientas de otro servidor confiable. Fijar un hash de nombre, descripción y esquema de entrada y verificarlo antes de cada llamada cierra las dos primeras y detecta la tercera.
¿Es obligatorio usar OAuth en un servidor MCP?
Para transportes HTTP remotos, en la práctica sí. La especificación indica que las implementaciones basadas en HTTP deberían seguir su modelo OAuth 2.1: el servidor actúa como resource server, debe implementar RFC 9728 y debe validar que los tokens presentados se emitieron para él. Los servidores stdio locales toman credenciales del entorno, pero siguen necesitando aislamiento y controles de consentimiento.
¿Qué es el token passthrough y por qué está prohibido?
Consiste en aceptar un token que no se emitió para ti y reenviarlo sin cambios a un servicio aguas abajo. La especificación MCP lo prohíbe expresamente: rompe la trazabilidad, evita controles que dependen de la audiencia del token y permite que cualquiera con un token robado use tu servidor como proxy de exfiltración. Si tu servidor MCP llama a una API superior, debe obtener su propio token para esa API.
¿Se está explotando alguna vulnerabilidad de MCP en el mundo real?
A fecha de publicación, ni CVE-2026-12537 ni CVE-2026-54316 aparecen en el catálogo KEV de CISA, y la información pública no muestra ninguna de las dos cadenas usada contra un objetivo. Existe desde junio de 2026 un repositorio público de reproducción del fallo de Claude Code, así que el factor limitante debería ser tu latencia de parcheo, no la disponibilidad del exploit.
¿Cómo evito que un servidor MCP malicioso alcance mi red interna?
La exposición está en el descubrimiento OAuth: el cliente descarga URLs que le proporciona el servidor, de modo que uno malicioso puede apuntarlas a endpoints de metadatos cloud o a servicios internos. Exige HTTPS fuera de loopback, bloquea rangos privados y link-local, aplica la misma validación a cada salto de redirección y enruta los clientes de servidor a través de un proxy de salida. No implementes la validación de IP a mano.
¿Qué son los Client ID Metadata Documents (CIMD)?
Es un mecanismo de registro de clientes añadido como valor recomendado en la revisión 2025-11-25 de MCP bajo SEP-991. El client_id es una URL HTTPS que resuelve a un documento JSON con la descripción del cliente y sus redirect URI, que el servidor de autorización descarga y valida bajo demanda. Evita tanto los client ID codificados a mano como la acumulación de registros desechables del Dynamic Client Registration.
Fuentes y lecturas
Texto de la especificación, RFC del IETF, material de proyectos OWASP, la hoja informativa de ciberseguridad de la NSA y avisos oficiales de los fabricantes usados en este artículo.
- Model Context Protocol — Authorization (specification 2025-11-25)
- Model Context Protocol — Security Best Practices
- Model Context Protocol — Key Changes, revision 2025-11-25
- SEP-991 — OAuth Client ID Metadata Documents for MCP
- RFC 9728 — OAuth 2.0 Protected Resource Metadata
- RFC 8707 — Resource Indicators for OAuth 2.0
- RFC 9700 — Best Current Practice for OAuth 2.0 Security
- OAuth 2.1 — draft-ietf-oauth-v2-1-13
- OWASP MCP Top 10 (beta, v0.1)
- OWASP Cheat Sheet Series — MCP Security
- OWASP Cheat Sheet Series — SSRF Prevention
- NSA Artificial Intelligence Security Center — CSI: MCP Security Design Considerations for AI-Driven Automation
- GHSA-fg94-h982-f3mm — Out-of-Band Data Exfiltration via Pre-Approved HuggingFace Domain in WebFetch
- NVD — CVE-2026-54316
- GHSA-wpqr-6v78-jr5g — Google run-gemini-cli security advisory
- NVD — CVE-2026-12537
- OpenAI — codex-action security guidance
¿Te ha resultado útil?