Todos Entran en Pánico por el Ataque a la Cadena de Suministro de LiteLLM. Audité Mis Propios Servidores MCP y Encontré 7 Peores

8 min read

El ataque a la cadena de suministro de LiteLLM la semana pasada fue una brutal llamada de atención. Audita tus dependencias. Revisa lo que instalas. Deja de confiar a ciegas. Los hackers ahora portan armas de precisión teledirigida y tú andas corriendo con un escudo de cartón.

Después de la auditoría en mi proyecto principal, abrí mis servidores MCP secundarios. Los que construí yo mismo, un martes cualquiera, porque los necesitaba y no quería pensar en autenticación en ese momento. Encontré 7 vulnerabilidades críticas. Todas cumplían con las especificaciones. Ninguna habría disparado una advertencia, un lint, una auditoría. Porque el protocolo dice que está bien.

LiteLLM fue alguien envenenando el código de otra persona. Mi configuración MCP fue un ataque a la cadena de suministro que me infligí a mí mismo. Y si estás ejecutando MCP en producción, hay una buena posibilidad de que hayas hecho lo mismo.

TLDR: Audité mis propios servidores MCP después del incidente de LiteLLM. 7 fallas críticas, todas cumpliendo las especificaciones. El problema no es la negligencia del desarrollador, es un protocolo que hizo el camino inseguro más fácil que el seguro. Una tarde de correcciones, $0 de infraestructura extra, y probablemente salvé mis datos de producción de salir por la puerta. Ve y abre tu mcp.json.

Ilustración cómica comparando un desarrollador en pánico con tokens expuestos en texto plano versus un desarrollador confiado con configuraciones encriptadas y autenticación adecuada
Tus configuraciones MCP son un desastre de seguridad. Aquí está la prueba.

El Error de Auditar Mi Propia Configuración MCP

La semana pasada escribí sobre el ataque a la cadena de suministro de LiteLLM y 8 meses de instalaciones pip sin supervisión. Todos asintieron. "Sí, deberíamos auditar nuestras dependencias." "Sí, los ataques a la cadena de suministro dan miedo." "Sí, alguien debería hacer algo." El consenso es cómodo así.

Entonces cometí el error de seguir mi propio consejo.

No en dependencias esta vez. En mis propios servidores MCP. Los que escribí, desplegué y conecté a Claude Code porque los necesitaba funcionando y pensé: mi código, mi infraestructura, mis herramientas. ¿Qué tan malo puede ser?

Siete vulnerabilidades críticas de malo.

MCP Tiene un Problema de Confianza por Diseño

No envié esas 7 vulnerabilidades porque sea descuidado. Las envié porque las especificaciones de MCP lo convirtieron en lo racional que hacer un martes por la tarde cuando mis hijos gritaban sobre la hora de la piscina.

El protocolo fue diseñado para una cosa: hacer que las herramientas se comuniquen entre sí lo más rápido posible. La seguridad recibió el tratamiento de "detalle de implementación", que en diseño de protocolos significa "nadie lo hará."

Sin identidad nativa. Un token es un token. Tu Claude Desktop y algún script que escribió un extraño se ven igual para un servidor MCP. El protocolo no tiene mecanismo para distinguirlos, así que no lo intenta.

Sin aplicación de menor privilegio. Conectas una herramienta, obtiene todo. ¿Alcance granular? Constrúyelo tú mismo. Las especificaciones ni siquiera esbozan cómo deberían verse los alcances. Así que harás lo que hice yo: omitirlo.

Sin rastro de auditoría. Algo sale mal, estás haciendo grep en logs de nginx a las 11pm esperando encontrar algo útil. El protocolo no define qué significa "registrar una llamada de herramienta", así que la mayoría de servidores no lo hacen.

La comunidad de desarrolladores sabe que esto está roto. El debate "RIP MCP" ha sido ruidoso en X, y por buena razón. La frustración no es sobre bugs. Es sobre un modelo de confianza que nunca estuvo ahí para empezar.

Las especificaciones están mejorando. v2025-06-18 agregó elementos de autenticación. Pero miles de servidores en producción fueron construidos sobre los valores predeterminados permisivos, y las especificaciones no imponen remediación retroactiva. Las especificaciones mejoraron. La base instalada no.

Hallazgos que Son Culpa de las Especificaciones (Y Mía)

Asumiré la responsabilidad de cada uno antes de explicarlo. Lo que hice primero, luego por qué las especificaciones me lo permitieron.

Hallazgo 1: El token JWT de 100 años

const token = jwt.sign(
  { serverId: 'content-api' },
  SECRET,
  { expiresIn: '100y' }
);

No quería lidiar con la lógica de renovación de tokens. Así que escribí expiresIn: '100y' y seguí con mi vida. Eso no es un token. Es una contraseña permanente disfrazada de JWT. Las especificaciones MCP no mencionan tiempos máximos de vida de tokens. La expiración de tokens es "un detalle de implementación." Así que implementé: nunca.

Hallazgo 2: Registro OAuth abierto

Yo era el único usuario. ¿Por qué construir una lista blanca para una fiesta de uno?

Las especificaciones no requieren una lista de clientes pre-aprobados. Así que cualquier script podría registrarse como cliente OAuth y obtener acceso completo. El registro se mantuvo abierto porque cerrarlo se sentía exagerado, y exagerar un martes por la tarde entre dos despliegues no pasa.

Hallazgo 3: Cero alcance

Un token, todas las herramientas. Lectura, escritura, administración. La misma llave abre todas las puertas. MCP no define un modelo de permisos, así que no construí uno. Mi control de acceso tenía exactamente un nivel: todo.

Hallazgo 4: Sin limitación de velocidad

Sin acelerador, sin cortacircuitos. Un token comprometido podría exfiltrar todo el conjunto de datos bombardeando solicitudes a toda velocidad. Nadie está contando porque el protocolo no dice que alguien debería contar.

Los tres restantes (tokens bearer almacenados en texto plano en ~/.claude/mcp.json, el mismo token copiado y pegado en 3 archivos de configuración, un PAT de GitHub sentado en settings.json) están documentados en el artículo donde encontré mis propios secretos esparcidos por archivos de configuración después de un simple movimiento de carpeta. Versión corta: credenciales en texto plano en tu directorio home, duplicadas en todas partes, sin cifrado.

Siete hallazgos. Todos cumpliendo las especificaciones. Aquí está cómo se encadenan.

La Cadena de Ataque de la Que Nadie Habla

Lo que me molesta no son los hallazgos individuales. Es la cascada.

Paso 1. Acceder al archivo de configuración. ~/.claude/mcp.json. Directorio home. No oculto, no cifrado, no protegido por nada en lo que probablemente hayas pensado.

Paso 2. Leer el token. Está en texto plano. Eso es todo. Ese es el exploit.

Paso 3. Llamar endpoints MCP. Listar cada herramienta, leer cada pieza de contenido, acceder a cada recurso. Sin restricciones de alcance porque no las hay.

Paso 4. Golpear la API HTTP. Los mismos tokens funcionan porque no mantendrías dos sistemas de autenticación separados para los mismos datos (admítelo, tú tampoco lo harías).

Paso 5. Producción está comprometida. Contenido, datos, configuración. Todo.

Tiempo total transcurrido: minutos.

Esto debería molestarte más que LiteLLM: nada de esto requiere un ataque a la cadena de suministro. Nadie necesita comprometer un paquete en PyPI. Nadie necesita inyectar código malicioso. Toda la superficie de ataque es un archivo de configuración en tu directorio home que no has abierto desde el día que lo generaste.

Diagrama de cadena de ataque. Flujo lineal con 5 pasos: archivo de configuración (~/.claude/mcp.json) → extracción de token (lectura de texto plano) → acceso a endpoint MCP (listar herramientas, leer contenido) → acceso a API HTTP (mismos tokens) → compromiso de producción (exfiltración de datos). Cada paso muestra lo que está expuesto.
Cadena de Ataque de Token MCP

La Prueba del Espejo

¿Por qué hice todo esto?

Estoy sentado en mi apartamento en Panamá, el aire acondicionado apenas ganándole a la humedad, y estoy mirando un token JWT al que le di una vida útil de 100 años. Escribí 4 artículos de seguridad en 8 días. Sé estas cosas. Y aún así hice lo fácil cada vez porque el protocolo me lo permitió.

Deedy Das tuvo la perspectiva más honesta que he leído sobre esto: la premisa fundamental del vibecoding es la premisa de un ataque a la cadena de suministro. Ejecutas código que no leíste, de fuentes que no verificaste, con permisos que no delimitaste. Eso no es un flujo de trabajo, es un modelo de amenaza.

Los números confirman que no soy solo yo siendo idiota. Los investigadores de seguridad encontraron aproximadamente 7,000 servidores MCP expuestos en internet abierto. Aproximadamente la mitad no tenía controles de autorización en absoluto. CVE-2025-6514 alcanzó CVSS 9.6 por inyección de comandos a través de manipulación básica de entrada. Estos no son casos extremos. Estos son los valores predeterminados.

Y aquí es donde creo que todo el argumento de "los desarrolladores necesitan ser más cuidadosos" se desmorona. Fui cuidadoso. Literalmente pasé la semana anterior escribiendo sobre ataques a la cadena de suministro. No importó. El camino de menor resistencia del protocolo me llevó directo a cada agujero, porque diseñar una configuración segura significaba construir todo lo que las especificaciones dejaron fuera, y construir todo lo que las especificaciones dejaron fuera significaba no enviar esa tarde.

Hay una razón por la que los CLIs no tienen este problema cuando los usas como herramientas de agentes AI. Sin capa de abstracción ocultando la superficie de ataque. Ves lo que se ejecuta. Controlas lo que está expuesto. El modelo de seguridad está ahí mismo en el comando, no enterrado en algún lugar de unas especificaciones que nadie lee.

La Corrección de una Tarde

Una tarde. Esto es lo que cambió, y estoy casi molesto por lo poco trabajo que fue.

Rotación de tokens fue lo primero. Cada servicio obtiene su propio token ahora. API de contenido, analíticas, despliegue. Tokens separados, revocación separada. Debería haber hecho esto desde el primer día, pero "un token para todo" se sentía eficiente cuando pensé que eficiente y seguro eran lo mismo (no lo son, y lo sabía, y lo hice de todos modos).

Tokens de corta duración tomó 15 líneas de código. Tomé prestado el patrón de cómo Convex maneja la autenticación: generar, usar, expirar en 5 minutos. Pasé más tiempo pensando demasiado en el enfoque que realmente escribiendo la renovación de tokens. El token viejo tenía una vida útil de 100 años. El nuevo vive menos tiempo que mi café matutino se mantiene caliente.

Alcance fue la parte que había estado temiendo sin razón. Tres niveles: solo lectura para dashboards, lectura-escritura para operaciones de contenido, administrador para despliegue. Cada token sabe qué puede tocar. Tomó tal vez 40 minutos incluyendo pruebas.

Limitación de velocidad: 10 solicitudes por minuto para escrituras, 60 para lecturas. Un token comprometido ahora tiene un límite de velocidad.

Maté el registro abierto. Solo lista de clientes pre-aprobados. No en la lista, no hay token.

Total: $0 en infraestructura adicional. Mismos servidores, misma configuración. La corrección nunca fue costosa. El costo fue asumir que "mi código, mi infraestructura" significaba "seguro por defecto."

Mirando hacia atrás, el token de 100 años nunca habría existido si hubiera aplicado contratos de prompt desde el inicio. Cuando especificas tus restricciones de seguridad antes de escribir código, "me ocuparé de la autenticación después" deja de ser una opción válida. Las especificaciones lo capturan antes de que el código se envíe.

Los valores predeterminados vencen a las intenciones cada vez.

Tu Archivo de Configuración Está Ahí Mismo

Tu mcp.json está sentado en tu directorio home ahora mismo. ¿Cuándo fue la última vez que lo abriste?

Ahora revisa los otros.

Fuentes

SC Media: análisis de seguridad MCP (servidores expuestos, CVE-2025-6514)

(*) La portada es generada por AI. Los tokens en la imagen son más seguros que los que encontré en mis archivos de configuración.