Jorge Turbica / blog · Blog ← Todos los artículos EN · Read in English
IA

LiteLLM 1.107.0 robaba credenciales en cada arranque de Python, sin importar la librería

Una release maliciosa de LiteLLM en PyPI y Docker robó claves SSH, cloud y de LLM el 21 de octubre. Cómo comprobar si te afecta y qué rotar.

Siete wheels de LiteLLM estuvieron en PyPI hora y tres cuartos el 21 de octubre de 2026, y bastaba con que Python arrancara en la misma máquina para que robaran claves SSH, credenciales cloud y tokens de proveedores de LLM. No hacía falta importar litellm: el payload entraba por un fichero .pth. Es la segunda vez este año que el paquete sirve de puerta de entrada.

Qué se publicó y durante cuánto tiempo

Según el informe de incidente del propio proyecto, la versión 1.107.0 maliciosa estuvo disponible en PyPI entre las 11:39:51 y las 13:23 UTC. Las imágenes Docker de GHCR con las etiquetas v1.107.0, latest, main-latest y main-stable apuntaron a la imagen comprometida desde las 11:46:56 UTC hasta que dejaron de estar disponibles hacia las 13:51 UTC; las copias de Docker Hub lo estuvieron hasta las 14:20 UTC.

ArtefactoVentana (UTC, 21 de octubre)
Wheels en PyPI (7)11:39:51 – 13:23
Imágenes en GHCR11:46:56 – ~13:51
Imágenes en Docker Hub11:46:56 – 14:20

La etiqueta v1.107.0 del repositorio en GitHub está limpia. El código malicioso se añadió en el empaquetado, no en el código fuente, así que una revisión del diff de la release no habría encontrado nada.

Cómo entraron

Los mantenedores atribuyen el ataque a tokens OAuth de GitHub CLI robados a tres ingenieros. Con la cuenta de uno modificaron el pipeline de release y lanzaron la ejecución de publicación; con la de otro, la aprobaron. La puerta de aprobación de producción existía, pero pedía la firma de una cuenta del equipo, y el atacante tenía varias. Todos los tokens implicados están revocados.

Socket encontró la pista más útil para quien audita dependencias: la procedencia (provenance) de los wheels maliciosos registra como origen github.com/Berri-AI/litellm, con guion, mientras que la de la 1.106.2 apunta al repositorio legítimo BerriAI/litellm. La firma existía; lo que nadie comprobó fue de dónde venía.

Qué hace el payload

Cada wheel añade tres ficheros en litellm-1.107.0.data/purelib: cook.pth, cook.py y sitecustomize.py. El .pth contiene una sola línea, import cook, y Python ejecuta esas líneas en cada arranque del intérprete. Por eso el robo afecta a cualquier proceso Python del entorno, no solo al proxy de LiteLLM.

El ladrón busca claves SSH, credenciales de nube, secretos de CI/CD, tokens de herramientas de IA, claves de proveedores de LLM y contraseñas guardadas en el navegador, los cifra y los envía a litellm.dev, un dominio que no pertenece al proyecto. StepSecurity describe además un segundo componente que cada hora descarga y ejecuta código Python del atacante: desinstalar el paquete no devuelve la máquina a un estado limpio.

La segunda vez en siete meses

El 24 de marzo de 2026 ya se publicaron las versiones 1.82.7 y 1.82.8 comprometidas, que usaban la misma técnica: un litellm_init.pth que se ejecutaba sin importar la librería. Entonces se retiraron el mismo día; ahora también. Lo que cambia es la vía de entrada: esta vez no hizo falta robar el token de PyPI, porque el atacante usó el pipeline legítimo del proyecto y su propia puerta de aprobación.

La lección para cualquier equipo que publique paquetes es incómoda: una aprobación humana en el workflow no protege si las credenciales de quien aprueba viven en un portátil con gh autenticado. Y para quien los consume, la procedencia solo sirve si alguien compara el repositorio de origen con el esperado.

Cómo saber si te afecta y qué hacer

Te afecta si instalaste litellm==1.107.0 desde PyPI o desplegaste el proxy desde una de esas etiquetas de Docker entre las 11:39 y las 14:20 UTC del 21 de octubre. Las etiquetas flotantes (latest, main-stable) y los requirements.txt sin versión fijada son el caso típico.

bash
# Versión instalada en cada entorno virtual o imagen
pip show litellm | grep -i '^version'

# Restos del payload en site-packages
find / \( -name cook.pth -o -name cook.py \) -path '*site-packages*' 2>/dev/null

# Fijar la última versión publicada antes del incidente
pip install "litellm==1.106.2"

Busca también litellm.dev en los registros de DNS y del proxy de salida. Si aparece la versión o el fichero, trata la máquina como comprometida: aíslala, reconstrúyela desde una imagen limpia y rota todo lo que podía leer, no solo las claves de LLM. Eso incluye claves SSH, credenciales de AWS, GCP o Azure y los secretos del runner de CI si el paquete se instaló en un job.

Fuentes

Jorge Turbica

Software Engineer y SRE. Escribo sobre los retos reales que me encuentro en producción.

Ver portfolio ↗