Principales conclusiones
- Hades incluía ganchos de inicio .pth, por lo que la carga útil se ejecutaba cada vez que se iniciaba Python, en lugar de una sola vez durante la instalación.
- Esa misma función ya se había incorporado a LiteLLM once semanas antes, por lo que la técnica no era nueva.
- Wheels se salta el paso de compilación del código fuente, pero aún así puede crear un archivo que ejecute código cada vez que se inicie el intérprete.
- La auditoría de tu propio entorno se reduce a unos pocos comandos, y la mayoría de los archivos .pth que encuentres serán legítimos.
Un desarrollador ejecuta «pip install», el comando finaliza correctamente y la tarea de CI se marca como completada. No se ha compilado nada ni se ha ejecutado ningún paso de compilación del código fuente, ya que el paquete se distribuyó como un archivo «wheel», y se suponía que los archivos «wheel» eran la opción más segura.
A continuación, se inicia el siguiente proceso de Python. Algo lee el archivo ~/.aws/credentials.
Esa es, a grandes rasgos, la estructura de la campaña «Hades», revelada por Orca Security el 8 de junio de 2026 a través de 26 paquetes de PyPI. Socket, al analizar los mismos 37 archivos «wheel» maliciosos, contabiliza 19 paquetes, por lo que la cifra que aparece en los titulares depende del inventario que se consulte. En cualquier caso, el código elude varios controles diseñados específicamente para reducir la ejecución de código durante la instalación, no mediante la explotación de un fallo, sino utilizando una característica que ya formaba parte de la biblioteca estándar desde antes de que existieran la mayoría de esos controles.
La anatomía de la campaña «Hades»
¿Qué diferencia a este caso de los habituales casos de caso de typosquatting no es ningún truco en concreto, sino la combinación de varios.
No se trataba de nombres falsos o similares que esperaban a que alguien cometiera un error al instalarlos. El patrón de publicación parece indicar que se produjo una apropiación de la cuenta del responsable del mantenimiento, seguida de una avalancha de lanzamientos consecutivos de parches en todo el catálogo de ese responsable, concentrados en Python científico, bioinformática y herramientas de aprendizaje automático. Los paquetes en los que los equipos ya confiaban comenzaron a distribuir nuevas versiones contaminadas.
Cada versión comprometida colocaba un archivo con el nombre *-setup.pth directamente en la carpeta «site-packages». En lugar de buscar carteras criptográficas o cookies del navegador, como hacen muchos programas de robo de información, la carga útil se centraba en las credenciales de la nube, los secretos de los desarrolladores y tokenes de CI/CD . El gancho de inicio es solo la primera fase. Descarga el entorno de ejecución de JavaScript «Bun» desde GitHub y ejecuta una carga útil de JavaScript ofuscada llamada _index.js. «Un gancho de inicio en Python que inicia un entorno de ejecución de JavaScript para robar claves de AWS» es una frase extraña de escribir, pero describe con precisión lo que ocurrió.
Algunas muestras también incluían un demonio de persistencia llamado «gh-token-monitor» que amenaza con llevar a cabo acciones destructivas si se revocan los tokens robados. El hecho de que exista malware diseñado para castigarte por aplicar correctamente los procedimientos de respuesta ante incidentes es algo tan novedoso que merece la pena pararse a reflexionar un momento.
Hades no surgió de la nada. Es el resultado del acuerdo sobre LiteLLM alcanzado en marzo de 2026 y la vulnerabilidad del paquete «durabletask» de la que se informó en mayo de 2026, lo que sitúa tres ataques destacados a la cadena de suministro de PyPI en un plazo inferior a tres meses. Los tres se han relacionado con la misma línea de campaña más amplia, a la que se hace referencia con nombres como TeamPCP, Mini Shai-Hulud y Miasma.
| Incidente | Notificado | Mecanismo |
| LiteLLM | Marzo de 2026 | Malicioso .pth enviado tras el compromiso del proceso de publicación de LiteLLM |
| durabletask (Microsoft) | May 2026 | Se ha utilizado un token de publicación de PyPI robado para publicar directamente tres versiones |
| Hades | Junio de 2026 | .pth ganchos de inicio en 26 paquetes, robo de credenciales, propagación similar a la de un gusano |
Esa línea de LiteLLM es la parte incómoda. El truco del «hook» para startups no era nada nuevo en junio. Ya se había distribuido en un paquete que alcanzaba millones de descargas al día allá por marzo, en forma de un archivo llamado litellm_init.pth, y once semanas después la técnica seguía permitiendo que los códigos maliciosos eludieran los controles diseñados para impedir la ejecución durante la instalación.
Por qué un archivo .pth se ejecuta antes que tu código
Este es el mecanismo, y resulta bastante impresionante, como a veces lo son las cosas feas.
Durante el inicio normal, el módulo `site` de Python procesa los archivos `.pth` que encuentra en los directorios de sitio configurados. Oficialmente, esto sirve para añadir directorios a `sys.path`, uno por línea. Sin embargo, existe un segundo comportamiento, mucho menos conocido, en el mismo módulo: cualquier línea que comience con «import» seguida de un espacio o una tabulación se ejecuta como código de Python.
Así que basta con una sola línea.
import os, base64; exec(base64.b64decode(‘…’))
Colócalo en un archivo con extensión .pth dentro de la carpeta «site-packages» y no hace falta que nada lo importe ni lo llame. Se ejecuta cada vez que se inicia un proceso de Python en ese entorno. En un servidor de CI con mucha actividad o en un portátil de desarrollo, eso supone decenas de veces al día, y se activa con pip, con python -c y con cualquier cosa que el servidor de lenguaje de tu editor esté haciendo en segundo plano.
¿Sabías que...? No se trata de un error ni de un exploit ingenioso. Es un comportamiento documentado en el módulo «site» de Python, que precede en años a las herramientas de empaquetado modernas, y que MITRE ahora clasifica como una técnica de adversario identificada, «Python Startup Hooks», con la sub-técnica T1546.018 dentro de la categoría «Event Triggered Execution».
Setuptools utiliza archivos .pth para algunas instalaciones personalizables, y coverage.py puede emplear el mismo mecanismo para iniciar Coverage automáticamente en subprocesos. Precisamente por eso funciona tan bien como vector de ataque. Un escáner que intente detectar un archivo .pth malicioso tiene que distinguirlo de alguna manera del archivo distutils-precedence.pth, que viene incluido con Setuptools y que, desde el punto de vista estructural, hace exactamente lo mismo por razones totalmente legítimas.
Por qué se suponía que las ruedas eran el formato seguro
Durante años, el consejo habitual ha sido dar preferencia a los archivos «wheel» frente a las distribuciones de código fuente, y el razonamiento sigue siendo válido. Instalar un sdist suele implicar compilarlo primero, y esa compilación ejecuta código controlado por paquetes a través de un backend conforme a la PEP 517, lo que, se mire como se mire, supone la ejecución de código arbitrario. Un archivo «wheel» ya está compilado, por lo que instalarlo consiste básicamente en descomprimirlo y colocar los archivos.
Ese consejo sigue siendo razonable, y, de todos modos, esta campaña funcionó. Un archivo «wheel» puede contener un archivo .pth, y al descomprimir un archivo «wheel», cualquier archivo .pth de nivel superior se coloca directamente en «site-packages».
La ejecución simplemente se retrasa.
| Mecanismo | Cuando se ejecuta el código | Se ejecuta durante la instalación o la compilación |
| Backend de compilación de sdist | Durante la instalación, en el momento de la compilación | Sí |
| npm postinstall | Durante la instalación | Sí |
| Rueda .pth gancho | Cada vez que se inicia el intérprete, tras la instalación | No |
| Rust build.rs | Durante la compilación | Sí |
Fíjate en la fila de «wheel». Una comprobación realizada durante la instalación que analiza el entorno y no detecta nada no significa que haya un fallo. En el momento de la instalación, aún no se ha ejecutado ningún código malicioso. La carga útil permanece en espera y se activa en la siguiente invocación de Python, lo que, en la compilación de un contenedor, podría no ocurrir hasta que la imagen ya se haya publicado y esté gestionando el tráfico.
Atención: Tanto el entorno aislado durante la instalación como la política de «solo ruedas» son controles útiles, pero ninguno de ellos por sí solo impide que un gancho .pth malicioso se ejecute más tarde, cuando Python se inicie fuera de ese entorno aislado. Si tu modelo de amenazas partía de la base de que esos dos controles cubrían la ejecución de código arbitrario, es necesario revisar esa suposición.
Uno de los puntos de control más rigurosos se sitúa antes incluso de que el artefacto llegue al entorno, lo que significa inspeccionar y verificar qué contiene realmente un paquete, en lugar de confiar en que el comportamiento durante la instalación lo indique. Recompilar los paquetes a partir del código fuente y analizar el resultado es una forma de lograrlo.
Lo que buscaba la carga útil
Principalmente, credenciales en la nube. Y el ámbito de búsqueda es mucho más reducido y predecible de lo que la mayoría de los equipos suponen, ya que los equipos de los desarrolladores o los ejecutores de CI suelen exponer secretos valiosos a través de una breve lista de ubicaciones bien conocidas: las credenciales y los archivos de configuración en ~/.aws; las credenciales predeterminadas de la aplicación en ~/.config/gcloud; el archivo kubeconfig en ~/.kube/config; y la autenticación del registro en ~/.docker/config.json. Cualquier variable de entorno que inyecte la plataforma de integración continua. Y en los servidores en la nube, el servicio de metadatos de instancias en 169.254.169.254. El informe de Hades también sitúa los tokens y las claves SSH de GitHub, npm, PyPI y RubyGems entre los primeros puestos de la lista, y así es como un ladrón de credenciales se convierte en algo que se propaga por sí solo.
Cuando es accesible, el servicio de metadatos es el verdadero tesoro. Es lo que puede convertir un portátil comprometido en una puerta de entrada a una parte mucho mayor del entorno de nube circundante. En un agente de compilación con un rol de IAM asociado, una carga útil con ejecución de código real, en lugar de solo SSRF, puede recuperar credenciales temporales para cualquier acción que ese rol esté autorizado a realizar: una sencilla llamada GET en configuraciones antiguas de IMDSv1 o un intercambio de tokens mediante PUT y luego GET cuando se aplica IMDSv2. En cualquier caso, no es necesario que exista nada en el disco.
Esa es la lógica estratégica de todo el asunto. Las credenciales extraídas de un entorno de compilación tienen más valor que las obtenidas del ordenador portátil de alguien, y los servidores de integración continua suelen recibir menos atención directa por parte de los usuarios que las estaciones de trabajo, a pesar de que contienen secretos de un valor excepcional.
La sección dedicada a los escáneres de IA
Varios informes sobre Hades han identificado la inyección indirecta de comandos dentro de los paquetes, código escrito específicamente para manipular la revisión de seguridad asistida por IA más que para engañar a la persona que lee la solicitud de incorporación de cambios.
Se trata de una novedad destacable, y solo tiene sentido como táctica si los atacantes dan ahora por hecho que hay un modelo de lenguaje grande (LLM) interponiéndose entre el paquete y el ingeniero. A mediados de 2026, esa suposición no es descabellada. Algunos equipos ya cuentan con un modelo que resume las diferencias entre dependencias antes de que un humano revise nada.
Esto plantea un problema delicado. Un modelo que lee el contenido de un paquete no fiable está, por definición, procesando datos controlados por un atacante. Debe tratarse igual que cualquier otra vía de entrada no fiable. Lo que no debe hacer es actuar como juez independiente para determinar si lo que acaba de leer es seguro. Considerar ese veredicto como definitivo es una dependencia circular disfrazada de medida de control.
Consejo de experto: Si un modelo revisa el contenido de las dependencias en cualquier punto de tu proceso, trata el paquete como una entrada no fiable y mantén su texto fuera del contexto de instrucciones del revisor. Combínalo con comprobaciones estáticas deterministas y no permitas que el veredicto del modelo sea el único filtro.
Cómo realizar una auditoría de tu entorno ahora mismo
Merece la pena hacerlo tanto si crees que te ha afectado como si no, y se trata más bien de unos cuantos comandos que de un proyecto en sí. Si quieres una respuesta rápida sobre la exposición, los listados de paquetes afectados se publican en los informes de Orca y Socket, y compararlos con tus archivos de bloqueo es mejor que ir a ciegas.
En primer lugar, localiza los directorios del sitio que realmente se procesan. Esto es importante porque el directorio «site-packages» de cada usuario también cuenta, y una lista que solo incluya los del sistema lo omitirá sin que nos demos cuenta.
python -m site
A continuación, en Linux o macOS, busca los archivos .pth que contengan líneas ejecutables. En Windows hay que realizar el mismo proceso en los directorios «site-packages» de tus instalaciones de Python y entornos virtuales.
find / -path «*site-packages*» -name «*.pth» \
-exec grep -lE “^import[[:space:]]” {} \; 2>/dev/null
Esa clase de espacios en blanco es importante, porque `site` solo ejecuta las líneas en las que «import» va seguido de un espacio o una tabulación, y un simple «^import» coincidirá sin problemas con una línea totalmente inocente que empiece por «importlib». Ya que estás en ello, la pasada específica de Hades consiste en buscar cualquier cosa que coincida con «*-setup.pth», y también merece la pena echar un vistazo a `sitecustomize.py` y `usercustomize.py`, ya que pertenecen a la misma familia de ganchos de inicio. Al parecer, algunos ejemplos de Hades inyectaban código en `__init__.py` en lugar de incluir un archivo `.pth`, por lo que una revisión centrada únicamente en los archivos `.pth` no garantiza que todo esté en orden.
Encontrarás resultados, y eso es normal. Es de esperar que aparezca distutils-precedence.pth de setuptools, __editable__*.pth de instalaciones editables y quizá uno o dos de bibliotecas científicas. Léelos de todos modos. Los archivos .pth legítimos suelen contener entradas de ruta o pequeños enlaces de importación generados por herramientas que puedes identificar. Los blobs codificados, las llamadas a exec(), la creación de subprocesos o cualquier cosa que acceda a la red merecen un análisis mucho más detallado, aunque ningún patrón por sí solo demuestra que un archivo sea malicioso en ningún sentido.
Para averiguar si el procesamiento del sitio web es la causa de algún comportamiento que estés investigando, inicia el intérprete con esa opción desactivada.
python -S -c «print(‘se ha omitido el procesamiento del sitio’)»
Si el comportamiento desaparece al utilizar la opción -S, el problema está relacionado con la inicialización del sitio, y los hooks de los archivos .pth son uno de los aspectos que hay que investigar. Esto reduce el ámbito de la búsqueda. Sin embargo, no demuestra qué mecanismo de inicio fue el responsable, ya que la opción -S desactiva mucho más que la ejecución de los archivos .pth.
Comprobar una rueda antes de que entre en contacto con el entorno requiere dos comandos.
pip download –no-deps somepackage
unzip -l somepackage-*.whl | grep ‘\.pth’
Un archivo «wheel» que incluya un archivo .pth no es necesariamente malicioso, y un archivo .pth no ejecuta nada de forma automática, ya que solo se ejecutan las líneas que comienzan con «import» seguido de un espacio en blanco. Sin embargo, un archivo «wheel» que contenga uno de estos archivos merece que se revise su contenido, y si hay una línea «import» ejecutable en él, conviene hablarlo antes de que acabe en tu archivo de control de acceso, en lugar de después.
Si encuentras algo, elimina el paquete y, a continuación, purga la caché de wheel, ya que un wheel corrupto que permanezca en la caché de pip o uv se reinstalará sin problemas la próxima vez que se busque esa versión. A continuación, renueva todo lo a lo que pueda acceder el entorno. Claves de la nube, tokens de GitHub, PyPI y npm, claves SSH, kubeconfigs. Las directrices de Orca sobre Hades consideran que la eliminación de paquetes y la rotación de credenciales son obligatorias, en lugar de opcionales, y esa es la interpretación correcta.
Por qué «pip install» no es el lugar adecuado para empezar a confiar
LiteLLM, durabletask y Hades difieren en los detalles, pero el patrón subyacente es idéntico. Ninguno de ellos necesitó una vulnerabilidad en la aplicación que utilizaba el paquete. Cada uno de ellos se aprovechó de una parte del proceso de empaquetado que el sistema considera de confianza por diseño, ya fuera un proceso de lanzamiento comprometido en el caso de LiteLLM, un token de publicación robado en el de durabletask o identidades de publicación secuestradas junto con un gancho de inicio documentado en el de Hades.
Una cosa que este patrón descarta es la «fijación de hash». El archivo malicioso «litellm_init.pth» se declaró correctamente en el propio RECORD del paquete, con un hash coincidente, por lo que el comando «pip install –require-hashes» se habría ejecutado sin problemas. Los hashes demuestran que el artefacto que has descargado coincide con el que aprobaste. No demuestran que el artefacto aprobado fuera inofensivo, ni que su editor no hubiera sido comprometido.
En la práctica, la respuesta depende del nivel de Python que estés utilizando.
¿Unos cuantos servicios? Revisa tus archivos .pth y establece un breve periodo de cuarentena para las nuevas versiones antes de adoptarlas, ya que estas campañas obtuvieron gran parte de su alcance inicial gracias a versiones maliciosas recién publicadas, antes de que los defensores tuvieran tiempo de reaccionar.
¿Utilizas Python en un gran conjunto de sistemas? La auditoría es un buen punto de partida, pero la medida arquitectónica más eficaz consiste en adelantar la comprobación de paquetes a una fase anterior a la instalación, de modo que sea menos probable que un artefacto malicioso llegue a la carpeta «site-packages» desde el principio.
Y si tus ejecutores de CI tienen credenciales de roles en la nube, empieza por ahí. Limita al máximo el alcance de esos roles, elimina las credenciales estáticas de larga duración y utiliza credenciales de corta duración siempre que la plataforma te lo permita, porque ahí es donde se concentra el alcance del impacto cuando una instalación defectuosa se convierte en un incidente.
Garantizar la seguridad de la cadena de suministro de Python
El hecho de adelantar esa filtración es la premisa fundamental en la que se basa SecureChain para Python. Los paquetes se recompilan a partir del código fuente y se comprueban antes de que lleguen a tu entorno, en lugar de analizarse una vez que ya se están ejecutando cada vez que se inicia el intérprete. Y la cobertura tampoco se detiene el día de la instalación.
Nuevas vulnerabilidades CVE se dan a conocer en paquetes que ya estás ejecutando y, con el tiempo, un responsable del mantenimiento abandona por completo el proyecto. SecureChain sigue aplicando parches a lo que realmente hay en tu pila y, en el caso de los paquetes del catálogo, esa cobertura continúa más allá de el fin de su vida útil , en lugar de dejarte en la disyuntiva de elegir entre una dependencia sin parchear y una reescritura forzada. Si actualmente una política de «solo wheels» o un análisis posterior a la instalación está desempeñando la función de línea de defensa, Hades es un buen motivo para comprobar dónde se sitúa realmente esa línea, tanto en el momento de la instalación como cada día a partir de entonces.
PREGUNTAS FRECUENTES
¿Qué es el malware de PyPI?
Código malicioso publicado en el Python Package Index, normalmente oculto dentro de un paquete que se hace pasar por uno legítimo, o dentro de un paquete legítimo cuya cuenta de editor haya sido comprometida. A menudo se ejecuta durante la instalación o al iniciar el intérprete, en lugar de esperar a que el código de tu aplicación lo llame.
¿Qué es un archivo .pth y por qué es peligroso?
Un archivo .pth ubicado en uno de los directorios del sitio de Python es procesado por el módulo «site» durante el inicio. Las líneas que comienzan con «import», seguidas de un espacio o una tabulación, se ejecutan, lo que significa que un archivo .pth puede ejecutar código arbitrario cada vez que se inicia un proceso de Python. Se trata de un comportamiento documentado y previsto, y tanto setuptools como las instalaciones editables lo utilizan de forma legítima, lo que precisamente hace que los archivos maliciosos sean difíciles de detectar.
¿Protegen las «wheels» contra paquetes de Python maliciosos?
Solo en parte. Las ruedas evitan el paso de compilación desde el código fuente que requiere la instalación de un sdist, y ese paso de compilación puede ejecutar código controlado por el paquete. No impiden que un paquete incluya un archivo .pth con una línea de importación ejecutable que se ejecuta cada vez que se inicia el intérprete.
¿Cómo puedo comprobar si un paquete ha instalado un gancho de inicio?
Busca en los directorios de «site» que utiliza Python para los archivos .pth aquellas líneas que empiecen por «import» seguidas de un espacio en blanco y, a continuación, léelas correctamente. Al ejecutar el intérprete con la opción -S se desactiva la inicialización automática de «site», incluido el procesamiento de los archivos .pth, por lo que esto ayuda a determinar si el comportamiento está relacionado con el proceso de inicio, sin demostrar qué mecanismo lo ha provocado.
¿Lo detecta pip-audit o algún escáner?
Depende de la herramienta. Los escáneres basados en alertas se basan en sus fuentes de datos subyacentes sobre vulnerabilidades y paquetes maliciosos, por lo que es posible que una versión maliciosa recién publicada no se detecte de inmediato. Las herramientas basadas en el comportamiento y en artefactos a veces detectan las amenazas más rápidamente, y la cobertura varía mucho de un proveedor a otro.




