Publicación maliciosa del paquete elementary-data en PyPI roba credenciales en la nube de ingenieros de datos
27 de abril de 2026
0 minutos de lecturaUn paquete de Python en PyPI llamado elementary-data, con más de un millón de descargas al mes, sufrió un ataque a la cadena de suministro mediante un vector de ataque de GitHub Actions.
Resumen
Aviso | |
Gravedad | Crítica (CVSS v4.0: 9.3) |
Paquete afectado |
|
Versiones seguras | Todas las versiones excepto |
Tipo de ataque | Cadena de suministro (inyección en CI/CD de GitHub Actions y luego paquete para robar credenciales) |
Credenciales robadas | Perfiles de dbt, credenciales de Snowflake/BigQuery/Redshift, claves de AWS/GCP/Azure, tokens de API, claves SSH, archivos |
Alcance | El paquete CLI de PyPI y una imagen de Docker fueron comprometidos; Elementary Cloud y el paquete Elementary dbt no se vieron afectados |
Indicador de detección |
|
Divulgación | 25 y 26 de abril de 2026 |
¿Qué es elementary-data?
elementary-data es una herramienta CLI de observabilidad de datos nativa de dbt que usan ingenieros de datos y analítica para monitorear el estado de las canalizaciones, detectar anomalías y dar seguimiento a las fallas de pruebas en almacenes de datos como Snowflake, BigQuery, Redshift y Databricks. El paquete registra aproximadamente 280 000 descargas por semana y más de 1.1 millones al mes, lo que lo sitúa entre las herramientas de datos más adoptadas.
El paquete ofrece integraciones con la mayoría de las principales plataformas de datos en la nube, precisamente lo que lo convirtió en un objetivo atractivo. Una herramienta que maneja habitualmente conexiones a Snowflake, BigQuery y AWS durante la ejecución de CI/CD está junto a muchas credenciales valiosas.
Cómo se desarrolló el ataque
El compromiso ocurrió en dos etapas: primero, se comprometió la canalización de publicación; después, se publicó contenido malicioso para robar más credenciales. Cabe señalar que este incidente de seguridad que afectó al paquete elementary-data es uno de los vectores más destacados que TeamPCP y otros actores de amenazas han explotado recientemente.
Etapa 1: Inyección de scripts en GitHub Actions
El 24 de abril de 2026, a las 22:10 UTC, un atacante que usaba una cuenta de GitHub creada dos días antes (realtungtungtungsahur) publicó un comentario manipulado en la solicitud de extracción (PR) #2147 del repositorio de elementary-data. El comentario aprovechó una falla de inyección de scripts en .github/workflows/update_pylon_issue.yml, un flujo de trabajo que procesaba eventos de comentarios en issues y solicitudes de extracción.
El bloque vulnerable run: interpolaba directamente ${{ github.event.comment.body }} en un script de shell antes de que bash lo analizara. Como esta expresión se expande en el momento de procesar la plantilla del flujo de trabajo, en lugar de depurarse como un argumento de cadena, es posible inyectar metacaracteres de shell o subcomandos en el cuerpo del comentario para ejecutar código arbitrario en el ejecutor. Cuando se activó el flujo de trabajo, la carga útil del atacante se ejecutó con acceso al GITHUB_TOKEN del repositorio.
Lo más importante es que el atacante no necesitó acceso de escritura directo al repositorio. El GITHUB_TOKEN disponible para el ejecutor tenía permisos suficientes para crear commits, enviar etiquetas y activar otros flujos de trabajo. El trabajo handle_comment inyectado permaneció activo durante dos horas y cuarenta y seis minutos, lo que le dio al atacante un período prolongado para preparar cada etapa posterior.
Con el token robado, el atacante falsificó un commit de lanzamiento con el hash b1e4b1f3aad0d489ab0e9208031c67402bbb8480. El commit se estructuró para parecer automatizado y oficial: era un commit huérfano (inaccesible desde cualquier rama), figuraba como creado por github-actions[bot], incluía una firma PGP "Verified" falsificada y usaba el mensaje release/v0.23.2 (#2188), copiado literalmente de una solicitud de extracción legítima que se había integrado nueve días antes. El atacante etiquetó este commit huérfano como v0.23.3 y luego activó el propio flujo de trabajo Release package del repositorio con tag=v0.23.3 como entrada. El paso de checkout de ese flujo de trabajo usó ref: ${{ inputs.tag || github.ref }}, así que compiló directamente desde el commit huérfano malicioso sin tocar master. La canalización legítima de CI/CD empaquetó y publicó el código malicioso. A las 22:20 UTC, elementary-data==0.23.3 ya estaba disponible en PyPI. Cuatro minutos después, se publicó una imagen de Docker comprometida (ghcr.io/elementary-data/elementary:0.23.3 y :latest, digest sha256:31ecc5939de6d24cf60c50d4ca26cf7a8c322db82a8ce4bd122ebd89cf634255).
Este vector de ataque se ha repetido varias veces en el ecosistema de PyPI. El ataque a la cadena de suministro de Ultralytics en diciembre de 2024 usó el mismo patrón de inyección de pull_request_target para robar credenciales y publicar cuatro versiones maliciosas. El compromiso de LiteLLM a principios de 2026 siguió una ruta algo distinta (una acción de GitHub de terceros manipulada), pero el resultado fue el mismo: tokens de PyPI robados para publicar un paquete que roba credenciales.
Etapa 2: El paquete malicioso
El atacante incrustó la carga útil maliciosa en un archivo llamado elementary.pth, incluido en el directorio site-packages del paquete.
Los archivos .pth son archivos de configuración de rutas de Python que site.py, el módulo de inicio de Python, procesa automáticamente cuando se inicia el intérprete. Toda línea de un archivo .pth que comienza con import se ejecuta como código Python al iniciar el intérprete, antes de que se ejecute tu propio código. Esto significa que el malware se activa cada vez que Python se inicia en el sistema afectado, incluso durante operaciones de pip install, y no solo cuando alguien importa explícitamente elementary.
Esta técnica también se usó en el compromiso de LiteLLM v1.82.8. Es más persistente y difícil de detectar que incrustar código malicioso en __init__.py, porque no requiere que la víctima importe el paquete manipulado. Basta con instalarlo.
Dentro de la carga útil: qué hizo el malware
El código incrustado en elementary.pth robaba credenciales y usaba tres etapas de cifrado: un envoltorio externo en base64, seguido de cifrado XOR basado en un flujo de claves MD5 (semilla: swabag) y una segunda capa de descifrado XOR. La ofuscación no es sofisticada según los estándares actuales del malware, pero es intencional: evita la detección simple basada en cadenas y aumenta el tiempo necesario para analizar lo que realmente hace el paquete.
Una vez que Python se iniciaba en una máquina afectada, la carga útil decodificada:
1. Recopilaba credenciales y secretos en todo el sistema de archivos, con el objetivo de obtener una amplia variedad de datos:
Perfiles de dbt (
~/.dbt/profiles.yml) y credenciales de almacenes de datos (Snowflake, BigQuery, Redshift, Databricks).Credenciales de proveedores de nube: AWS
~/.aws/credentials, además de credenciales activas de roles obtenidas del punto de conexión de metadatos IMDSv2, con llamadas directas firmadas con SigV4 a AWS Secrets Manager y SSM Parameter Store; GCPapplication_default_credentials.json; directorios de Azure~/.azure/.Claves privadas SSH (
id_rsa, id_ed25519, ~/.git-credentials).Secretos de contenedores y orquestación:
~/.docker/config.json,~/.kube/config,todos los archivos/etc/kubernetes/*.confy tokens de ServiceAccount de Kubernetes.Credenciales de administradores de paquetes:
~/.npmrc,~/.pypirc,~/.cargo/credentials.toml.Otros secretos almacenados: archivos
.env*(búsqueda de hasta seis niveles de directorios),~/.vault-token,~/.netrc,~/.pgpass,~/.my.cnfy tokens de API en variables de entorno.Archivos de billeteras de criptomonedas (Bitcoin, Litecoin, Dogecoin, Zcash, Dash, Monero, Ripple, Ethereum, Cardano y pares de claves de validadores de Solana).
Archivos del sistema:
/etc/passwd,/etc/shadow, archivos del historial de shell y/var/log/auth.log.
2. Empaquetaba todo el material recopilado en un archivo llamado trin.tar.gz y luego lo exfiltraba mediante curl --data-binary al servidor C2 en igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud, usando el encabezado HTTP X-Rise-To-The-Trinny: agree.
3. Dejaba un archivo marcador en $TMPDIR/.trinny-security-update (Linux/macOS) o %TEMP%\.trinny-security-update (Windows), como indicio de que el malware se había ejecutado al menos una vez.
El alcance de las credenciales va mucho más allá de dbt y los almacenes de datos. La carga útil está diseñada para buscar ampliamente los secretos a los que pueda acceder en la máquina, incluidos clústeres de Kubernetes, administradores de secretos de infraestructura y claves de criptomonedas. El objetivo en dbt y los almacenes de datos lo hace relevante para los usuarios de la herramienta, pero cualquiera que lo haya ejecutado en una máquina de desarrollo o un ejecutor de CI corre el riesgo de perder mucho más.
El perfil de credenciales coincide con el de los usuarios típicos de la herramienta. Es casi seguro que los ingenieros de datos que ejecutan la CLI de elementary-data la usan con un almacén de datos conectado y credenciales de proveedores de nube, a menudo en un entorno de CI/CD donde esas credenciales se guardan como secretos o variables de entorno. Se trata de un ataque dirigido, no de un ataque indiscriminado.
Impacto y alcance
El período del ataque comenzó el 24 de abril a las 22:20 UTC (cuando apareció el paquete en PyPI) y terminó cuando se eliminó el paquete el 25 de abril, entre las 8:51 y las 11:51 UTC, después de que miembros de la comunidad reportaran el problema a las 6:18 UTC. La exposición duró aproximadamente entre ocho y diez horas.
Quienes cumplan alguna de estas condiciones deben asumir que el malware se ejecutó y que sus credenciales fueron exfiltradas:
ejecutaron
pip install elementary-datao actualizaron el paquete durante ese período,usaron una imagen de Docker obtenida del registro de elementary-data entre el 24 de abril a las 22:24 UTC y el momento de su eliminación,
o tenían una canalización de CI/CD que obtenía automáticamente la versión más reciente
Elementary Cloud y el paquete Elementary dbt no se vieron afectados, y ninguna otra versión de la CLI contenía el código malicioso.
Detección: ¿estás afectado?
Paso 1: Revisa la versión instalada
Si el resultado muestra Version: 0.23.3, tu entorno estuvo expuesto.
Paso 2: Busca el marcador de ejecución
El malware crea un archivo marcador al ejecutarse:
Si el archivo está presente, significa que el código que roba credenciales se ejecutó en ese entorno. Su ausencia no garantiza que el entorno sea seguro; es posible que el malware no haya escrito el marcador en todas las rutas de ejecución o que se haya borrado el directorio temporal.
Paso 3: Revisa con Snyk
Para revisar si tus dependencias de Python incluyen este u otros paquetes maliciosos o vulnerables conocidos:
La base de datos de vulnerabilidades de Snyk incluye SNYK-PYTHON-ELEMENTARYDATA-16316110 y marcará cualquier entorno que aún tenga fijada la versión 0.23.3.
Nota: Te recomendamos consultar nuestra guía rápida de prácticas recomendadas de seguridad para Python y el artículo sobre prácticas recomendadas para crear contenedores de aplicaciones Python con Docker para aplicar pautas de desarrollo seguro.
Solución
1. Actualiza de inmediato
La versión 0.23.4 se publicó el 25 de abril de 2026 y no contiene código malicioso.
Si usas un archivo requirements.txt o pyproject.toml, actualiza la versión fijada:
2. Rota todas las credenciales que podrían haberse expuesto
Considera comprometidas todas las credenciales a las que podían acceder los procesos de Python en las máquinas afectadas. En concreto:
Perfiles de dbt: Rota las contraseñas del almacén de datos y los tokens OAuth en
~/.dbt/profiles.yml.Claves de proveedores de nube: Rota o revoca las claves de AWS IAM (y revisa los valores a los que se accedió en Secrets Manager y SSM Parameter Store), las claves de cuentas de servicio de GCP y las entidades de servicio de Azure.
Kubernetes: Rota los tokens de ServiceAccount y audita todos los archivos
/etc/kubernetes/*.confa los que se pudo acceder.Registros de contenedores: Rota las credenciales almacenadas en
~/.docker/config.json.Tokens de administradores de paquetes: Rota los tokens de
~/.npmrc,~/.pypircy~/.cargo/credentials.toml.Administradores de secretos: Rota los tokens de HashiCorp Vault (
~/.vault-token) y cualquier credencial de.netrc,.pgpasso.my.cnf.Claves SSH: Si había claves privadas en la máquina, considéralas expuestas y rótalas.
Secretos de CI/CD: Si la máquina afectada era un ejecutor de CI, rota todos los secretos almacenados en ese entorno.
Rotar las credenciales no es suficiente. Revisa los registros de acceso en busca del dominio de exfiltración igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud y de cualquier servicio tuyo para detectar accesos no autorizados que ya hayan ocurrido.
3. Borra las cachés de Python
4. Descarga imágenes de Docker limpias
La imagen comprometida (ghcr.io/elementary-data/elementary:0.23.3 y :latest) tenía el digest sha256:31ecc5939de6d24cf60c50d4ca26cf7a8c322db82a8ce4bd122ebd89cf634255. La última imagen que se sabe que estaba limpia es la 0.23.2, con el digest sha256:b3bbfafde1a0db3a4d47e70eb0eb2ca19daef4a19410154a71abee567b35d3d9. Descarga una imagen limpia compilada después del 25 de abril de 2026:
Verifica que no estés ejecutando una copia en caché de la imagen comprometida:
5. Audita tus flujos de trabajo de GitHub Actions
Si mantienes paquetes de Python, este incidente es un motivo para auditar cualquier flujo de trabajo que procese eventos de comentarios en issues o solicitudes de cambios. El patrón específico que debes buscar son expresiones de contexto sin comillas interpoladas directamente en bloques run::
La publicación de Snyk sobre las vulnerabilidades de GitHub Actions y el análisis del compromiso de TJ Actions abarcan los patrones más generales que debes buscar.
Además de sanitizar las entradas, una solución más duradera es eliminar por completo los tokens de API de PyPI de larga duración de los secretos de tu flujo de trabajo. PyPI admite Trusted Publishers, que usan tokens OIDC de corta duración con alcance limitado a un flujo de trabajo específico en un repositorio específico: tokens que no se pueden exfiltrar y reutilizar. El atacante de elementary-data necesitó un secreto de larga duración para publicar; Trusted Publishers elimina esa superficie de ataque. Además, protege los flujos de trabajo de publicación privilegiados con aprobaciones manuales que requieran confirmación humana antes de ejecutar el paso de publicación.
El patrón que se repite
Este ataque sigue un método que ya resulta familiar: encontrar una brecha en la configuración de GitHub Actions de un proyecto, inyectar código que robe el token de publicación de PyPI, usarlo para publicar una versión maliciosa e incluir un archivo .pth o un mecanismo de inicio similar para maximizar el alcance.
El mismo patrón apareció en el ataque a Ultralytics (diciembre de 2024, inyección en una rama mediante pull_request_target, minero de criptomonedas), el ataque a LiteLLM (principios de 2026, acción de Trivy envenenada, ladrón de credenciales con puerta trasera persistente) y el incidente de Cline/Clinejection (inyección de prompts asistida por IA en Actions, tokens robados).
El patrón no es nuevo. Las herramientas para explotarlo se conocen bien y parecen usarse de forma activa y repetida. Para quienes mantienen paquetes, la prioridad es reforzar los flujos de trabajo: restringir the use of pull_request_target, exigir aprobación manual para los flujos de publicación, usar tokens OIDC de corta duración para publicar en PyPI en lugar de tokens de API de larga duración e implementar reglas de protección de ramas para evitar activaciones de publicación no autorizadas.
Para quienes usan elementary-data, el equipo de Elementary respondió rápidamente: pasaron menos de cuatro horas desde el informe de la comunidad hasta la mitigación inicial, y el equipo publicó un informe completo del incidente. Vale la pena tener en cuenta la rapidez de la respuesta junto con el incidente en sí.
Protege tu cadena de suministro con Snyk
Los problemas de seguridad de la cadena de suministro afectaron al 87 % de las personas encuestadas. Protege la tuya con Snyk.



