Volver al Blog

El SDK de tus agentes de IA tiene seis avisos nuevos. Tu escáner dice cero

Escritorio con un portátil de pantalla apagada, una libreta abierta con anotaciones a mano, un bolígrafo y una taza de café
La pantalla apagada y la libreta abierta: cuando el canal automático no trae nada, el aviso sigue existiendo, pero hay que ir a buscarlo a mano. Foto: Home-office-336377, por Free-Photos (Pixabay), vía Wikimedia Commons, CC0. Oscurecida y con rótulos superpuestos.

Esta mañana hemos hecho dos llamadas a dos APIs públicas. La primera le pregunta al repositorio del SDK de Python del Model Context Protocol —la pieza que conecta los agentes de IA con los sistemas donde trabajan— cuántos avisos de seguridad tiene publicados: devuelve doce. La segunda le pregunta lo mismo a OSV, la base de datos pública que alimenta a media industria de escáneres de dependencias: devuelve seis. Los seis que faltan son los seis últimos.

Se publicaron entre el 28 de septiembre y el 5 de octubre de 2026: ocho días, seis avisos. Tres puntúan 7,5 sobre 10. Hoy, 12 de octubre, ninguno de los seis existe en OSV, ninguno existe en la base global de avisos de GitHub, ninguno sale por la API de PyPI y el único que lleva CVE asignado —CVE-2026-59951— tampoco está en el NVD. Existen en un sitio: la pestaña de seguridad del repositorio.

La frase «el escáner no ha saltado» lleva dentro una suposición que casi nunca se mira de frente: que lo que se sabe de un fallo y lo que llega a tu inventario son la misma cosa. Aquí no lo son, y la diferencia se puede medir. El proyecto, por cierto, ha hecho su parte; volveremos a eso al final.

La comprobación, para que la repitas

Dos llamadas sin autenticar, sin cuenta y sin herramientas. La primera pregunta al repositorio; la segunda, a OSV:

# Avisos publicados en el repositorio
curl -s "https://api.github.com/repos/modelcontextprotocol/python-sdk/security-advisories" \
  | jq -r '.[] | "\(.published_at[0:10]) \(.ghsa_id) CVE:\(.cve_id // "-")"' | sort

# Avisos que OSV conoce para el paquete mcp
curl -s -X POST "https://api.osv.dev/v1/query" \
  -d '{"package":{"name":"mcp","ecosystem":"PyPI"}}' | jq -r '.vulns[].id' | grep GHSA | sort

Doce frente a seis. El grep de la segunda llamada no es un truco para que salga el número: OSV devuelve doce entradas, pero son esos mismos seis avisos listados dos veces, una con su identificador GHSA y otra con su alias PYSEC —PYSEC-2026-1616 y compañía, cada uno con el GHSA antiguo en su campo aliases—. Seis vulnerabilidades distintas, todas anteriores a septiembre. Y el control que convierte esto en un dato y no en una anécdota: pregúntale a PyPI por una versión antigua y por una reciente. La API de PyPI es, además, la fuente que pip-audit consulta por defecto —su propia documentación lista --vulnerability-service con (choices: osv, pypi, esms) (default: pypi)—.

$ curl -s https://pypi.org/pypi/mcp/1.12.0/json | jq '.vulnerabilities | length'
6

$ curl -s https://pypi.org/pypi/mcp/1.29.1/json | jq '.vulnerabilities | length'
0

La versión de 2025 devuelve seis vulnerabilidades. La 1.29.1, que es la última de la rama 1.x anterior al arreglo, devuelve cero. Y cinco de los seis avisos nuevos la nombran como afectada en sus rangos de versiones. Lo mismo ocurre con la 2.1.1 en la rama 2.x: cero vulnerabilidades según PyPI, cinco de los seis avisos la incluyen. El paquete está en los feeds; estos seis avisos no.

La explicación es aburrida y por eso se pasa por alto: GitHub distingue entre el aviso que un proyecto publica en su repositorio y el que además se publica en la base de datos global. Solo el segundo se replica a OSV y dispara alertas: la documentación de Dependabot lo dice en una línea —«Only advisories reviewed by GitHub trigger alerts»—. Los seis están en el primer estado. Ninguna de las dos partes ha hecho nada raro. El resultado, aun así, es que un escáner correcto, bien configurado y actualizado te devuelve cero.

Qué hay en los seis

Merece la pena leerlos porque no son fallos de laboratorio, y porque se reparten a partes iguales: tres de los seis solo te afectan si tu código actúa como cliente MCP —si tienes un agente que se conecta a servidores que no controlas del todo— y los otros tres, solo si eres tú quien levanta el servidor. Cada ficha lo delimita en su apartado Am I affected?, y los de servidor dicen expresamente «Not affected: MCP clients».

  • 28-09 · 7,5 · GHSA-qx49-fqc8-xw99. El servidor MCP al que te conectas decidía a qué servidor de autorización mandaba tu cliente las credenciales. Se lleva el client_secret, el código de autorización y el code_verifier de PKCE.
  • 28-09 · 6,5 · GHSA-rwrf-2pqf-9j8j. Al validar el resultado de una herramienta, el cliente resolvía las referencias $ref del esquema abriendo la URL. Un servidor podía hacerle pedir una dirección interna o leer un fichero local, y dejarlo colgado: «the fetch ran synchronously on the event loop with no timeout».
  • 30-09 · 7,5 · GHSA-84m7-p3x7-pcfv (CVE-2026-59951). Las sesiones de Streamable HTTP no se reclamaban nunca: no caducaban, un DELETE dejaba la entrada registrada y cada sesión guardaba sus parámetros de initialize. Solo un reinicio liberaba la memoria. El propio aviso es honesto con su arreglo: «This bounds retention rather than eliminating it».
  • 30-09 · 7,5 · GHSA-fmmv-w9g8-j3gc. El servidor leía el cuerpo entero de cada petición en memoria antes de mirar su tamaño, también en los endpoints de OAuth y sin necesidad de credenciales. Este es el único de los seis cuyo arreglo es anterior: llegó en la 1.29.1 del 24 de agosto.
  • 02-10 · 5,9 · GHSA-5h93-6whr-6q8j. Los transportes HTTP seguían redirecciones a cualquier host llevándose las cabeceras propias —el aviso pone de ejemplo un X-API-Key— y, en un 307 o un 308, también el cuerpo. Basta con un dominio caducado al final de una redirección vieja.
  • 05-10 · 6,8 · GHSA-w4fh-qvv9-3v23. La autenticación por token del servidor no comprobaba para quién se había emitido el token. Si tu servidor de autorización emite tokens para varios servicios, el de otro servicio también entraba.

Dos de los seis no se arreglan actualizando

Aquí está la parte que de verdad cambia el trabajo de alguien, y es la que peor viaja por cualquier feed automático, porque ningún feed tiene un campo para «además hay que editar código». Dos de los seis lo dicen con todas las letras.

El del token ajeno, GHSA-w4fh-qvv9-3v23, abre su apartado de arreglo así: «Upgrading alone changes nothing. The patched versions add a validate_token_resource setting that is off unless you set it.». Y avisa de la trampa de hacerlo a medias: hay que activar la opción y devolver la audiencia del token desde verify_token(); con solo lo primero, «every request gets 401». Es decir: o lo haces entero, o no haces nada, o te quedas sin servicio.

El de las credenciales OAuth, GHSA-qx49-fqc8-xw99, el de 7,5, dice lo mismo para dos de sus proveedores: «upgrading changes nothing until you also pass issuer=». Sin ese parámetro «they still follow whichever authorization server the MCP server advertises». ¿Y cómo te enteras de que te falta? Con un aviso que el propio texto desactiva en la misma frase: «On 1.30.0 it is a DeprecationWarning, which Python hides by default». La señal existe, está apagada de fábrica y la nota lo reconoce por escrito.

Ese mismo aviso añade dos tareas que no hace ningún gestor de paquetes: borrar el registro OAuth guardado, porque «a registration stored without an issuer stays unbound», y rotar el secreto y revocar los tokens si ese cliente pudo conectarse a un servidor que no controlas. Un escáner que te dijera «actualiza a 1.30.0» y se callara el resto te dejaría con la sensación de haber terminado.

El arreglo es del 7 de septiembre; el aviso, del 28

Las versiones que cierran cinco de los seis —1.30.0 y 2.2.0— se subieron a PyPI el 7 de septiembre de 2026. Los avisos que explican qué cerraban llegaron veintiún días después el primero y veintiocho el último. Durante esas tres o cuatro semanas la información existía, pero con otro nombre: las notas de las dos versiones describen los cambios bajo el epígrafe «Behaviour changes», no bajo «seguridad». En la 1.30.0 se lee, literal, «HTTP client redirects are only followed within the endpoint's origin», «Idle Streamable HTTP sessions now expire» y «The OAuth client checks the authorization server's issuer»; en la 2.2.0 los dos últimos llevan además una coletilla sobre la ruta heredada. Son los mismos tres fallos, contados como ajustes de comportamiento.

No es un reproche: un proyecto puede arreglar primero y redactar la ficha después, y es mejor eso que al revés. Lo que deja ver es el orden real en el que se entera la gente. Quien actualiza por costumbre quedó cubierto el 7 de septiembre sin saber de qué. Quien lee los cambios antes de actualizar leyó «cambio de comportamiento» y lo metió en la cola de lo que no corre prisa. Y quien espera a que salte una alerta sigue esperando hoy.

La nota sube si no hay nadie delante

Hay un detalle en dos de las fichas que no habíamos visto antes: el mismo fallo lleva dos notas. Dos de los tres avisos de cliente se puntúan dos veces, según haya o no una persona delante aprobando. El de los $ref lo escribe así: «The rating assumes a person added the server and approves tool calls. For unattended agents, scored without user interaction, it is 7.5». El de las credenciales va al revés: puntúa 7,5 para los proveedores desatendidos y aclara que «with the interactive OAuthClientProvider a person has to start the sign-in; scored that way it is 6.5».

Traducido a tu inventario: el mismo fallo, en el mismo paquete, vale una cosa en el portátil de un desarrollador que aprueba cada llamada y otra distinta en el proceso que corre solo a las tres de la madrugada. Y el que corre solo a las tres de la madrugada es, justamente, el que montas cuando la automatización empieza a funcionar bien. La supervisión humana no es un detalle de implantación: es una variable de la puntuación, escrita por quien conoce el código. De eso iba también El agente de IA no saldrá en tu registro: saldrá quien le prestó el token: el agente hereda los permisos de quien se los prestó, y el registro apunta a la persona equivocada.

Lo que cambia en la práctica

Un escáner de dependencias responde a una pregunta muy concreta: «¿hay en los feeds públicos un aviso que nombre esta versión?». La pregunta que a ti te importa es otra, «¿se sabe algo de esta pieza?», y nadie la responde por ti. Tres cosas que sí se pueden hacer esta semana:

  • 1Haz la lista de las piezas que automatizan tu empresa, con su versión. El orquestador, los SDK, los servidores MCP que has levantado, el conector que escribió alguien en marzo. Si no está en una lista con nombre y versión, no está en ningún inventario, y lo que no está en un inventario no se parchea: se descubre.
  • 2Para cada una, apunta dónde se publican sus avisos. No «en mi escáner»: la dirección concreta. En GitHub es /security/advisories del repositorio, y se consulta con la misma llamada de arriba desde un cron semanal. Ojo, que ni siquiera hay atajo: GitHub sirve un Atom para las releases de un repositorio, pero pedirle el de los avisos devuelve un 406. El inventario se levanta una vez; la consulta, luego, va sola.
  • 3Después de actualizar, lee si el arreglo pide además tocar código. Es la casilla que no existe en ningún panel. Aquí son dos de seis, con la frase «upgrading alone changes nothing» escrita por los propios autores. Si tu proceso de parcheo termina cuando cambia el número de versión, en esos dos casos termina antes de arreglar nada.

Nosotros lo aplicamos a lo nuestro, que es pequeño y por eso sirve de ejemplo: la automatización interna corre sobre n8n autoalojado, y lo tratamos como lo que es, un servicio de producción —inventariado, con dueño y con ventana de actualización—, no como un script que alguien levantó un viernes. Esa es toda la diferencia entre una pieza que se parchea y una pieza que se descubre el día que falla. Es el mismo hueco que contábamos en Tu política de parcheo no contempla que no haya parche, visto desde el otro lado: allí había aviso y no había parche; aquí hay parche y no hay aviso.

Lo que no vamos a decirte

  • ✗Que el proyecto lo haya hecho mal. Las seis fichas son de las mejores que hemos leído este año: rangos de versiones exactos, un apartado de «Am I affected?» que descarta casos, mitigaciones para quien no puede actualizar y la advertencia explícita de que actualizar no basta. Mucha gente con más presupuesto publica bastante menos.
  • ✗Que tu escáner esté roto o que haya que cambiarlo. Está haciendo bien su trabajo con los datos que recibe. El agujero no está en la herramienta, está entre la publicación y el feed, y ninguna herramienta lo tapa sola.
  • ✗Cuántos proyectos más están en esta situación. No lo sabemos: hemos mirado uno, con cuidado, y contamos lo que hemos medido. Si quieres saber si tus dependencias críticas están igual, la comprobación de arriba tarda un minuto por paquete y la respuesta es tuya, no nuestra.

El mes pasado se descargó este paquete doscientos treinta y cinco millones de veces, según las estadísticas públicas de PyPI —una cifra que incluye integración continua, réplicas y reinstalaciones, así que no son doscientos treinta y cinco millones de instalaciones vivas—. Aun dividiéndola por lo que quieras, queda una cantidad enorme de sitios donde alguien contestó «el escáner no ha dicho nada» y tenía razón.

Fuentes (comprobadas el 12-10-2026): los recuentos de doce avisos en el repositorio y seis en OSV, los códigos 404 de los seis recientes tanto en api.osv.dev como en la base global de GitHub, los cero resultados de CVE-2026-59951 en el NVD y las cifras de la API de PyPI son nuestros, obtenidos con las llamadas públicas reproducidas en el artículo. Las citas entrecomilladas son literales de los avisos del repositorio: modelcontextprotocol/python-sdk · Security Advisories. Notas de versión de 1.30.0 y 2.2.0 (7-09-2026): releases del proyecto. Fuente por defecto de pip-audit: README de pypa/pip-audit. Condición de alerta de Dependabot: About Dependabot alerts. Descargas del último mes: pypistats.org/packages/mcp. Si repites las llamadas semanas después de esta fecha, los números pueden haber cambiado: eso sería una buena noticia, y el método para verificarlo es el mismo.

¿Sabes qué versión de qué pieza mueve tu automatización ahora mismo?

Si la respuesta tarda más de un minuto, el inventario es el trabajo, no el parche. Es lo que hacemos en automatización e IA —poner dueño, versión y ventana a cada pieza que trabaja sola—, lo que lo mantiene vivo después es el mantenimiento informático, y lo que acota el daño cuando una credencial acaba donde no debe es Zero Trust: que el token del agente no abra más puertas de las que necesita.

Hablar con everyWAN

Etiquetas:

Compartir:

Suscríbete a nuestra newsletter

Para recibir historias del mundo IT, novedades de everyWAN y ofertas exclusivas para suscriptores, date de alta a nuestra lista de correo

everyWAN
everyWAN