On April 30, 2026, two releases of lightning — the deep learning framework formerly published as pytorch-lightning and pulling roughly 311,000 downloads a day — shipped a hidden _runtime directory that had nothing to do with training neural networks. Versions 2.6.2 and 2.6.3 downloaded the Bun JavaScript runtime from GitHub the moment the package was imported, then used it to execute an approximately 11MB obfuscated JavaScript payload built to harvest developer and cloud credentials. The malicious releases were live for about 42 minutes before PyPI quarantined them, according to Snyk and IBM X-Force reporting, but that was long enough to reach anyone who happened to pip install or auto-upgrade during the window. Six weeks later, in June 2026, a related campaign dubbed Hades — a branch of the Shai-Hulud/Miasma worm lineage — poisoned 19 PyPI packages across bioinformatics, graph-ML, and dev-tooling categories using the same Bun-execution trick, but triggered from a .pth file instead of an import. Both incidents point at the same blind spot: Python supply-chain tooling watches Python code, and a compiled JavaScript runtime binary walks right past it.
What actually happened in the lightning compromise?
An attacker with valid PyPI publishing credentials pushed tampered builds directly to the registry as lightning 2.6.2 and 2.6.3, bypassing the project's GitHub source control entirely — the malicious code never appeared in the public repository. The last clean release was 2.6.1, published January 30, 2026. Buried inside the wheel was a _runtime directory that, on import, downloaded a Bun binary from GitHub's release infrastructure and used it purely as an execution engine — Bun never touched the model training code, it just ran an obfuscated .js payload. Socket's analysis of the deobfuscated script found roughly 703 references to process and env, more than 463 references to tokens and authentication, and 336 references to repositories, consistent with systematic credential harvesting rather than a narrow, targeted theft.
Why would attackers bundle a JavaScript runtime inside a Python package?
Because a Python virtual environment usually doesn't have Node.js installed, and the attacker can't assume it does. Bun ships as a single portable binary with no separate runtime dependencies, so fetching it at import time turns any Python environment — a laptop, a CI runner, a Kubernetes job — into a JavaScript execution host on demand. That matters because a large share of publicly available credential-stealer tooling, obfuscators, and packers target the Node/JS ecosystem, not Python; reusing that tooling against Python victims is cheaper than writing and obfuscating an equivalent payload in native Python, which security tooling built around pickle, eval, and exec patterns is already tuned to catch. The Bun binary is functionally a foreign interpreter smuggled inside a pip install, and until this generation of attacks it fell outside what most Python-specific scanners were built to look for.
How did the Hades campaign change the execution trigger?
Hades, tracked as a branch of the Shai-Hulud/Miasma worm family, dropped its payload through a *-setup.pth file rather than package-import code. Earlier waves in the same lineage were linked with high confidence to a threat actor known as TeamPCP, but the group open-sourced its worm toolkit in May 2026, and researchers — including Socket's team — have not attributed the Hades wave itself to a specific actor. Python's site module executes any line in a .pth file beginning with import via exec() on every single interpreter startup — not just when the poisoned package itself is imported. Socket.dev and The Hacker News documented an initial wave of 19 poisoned PyPI packages spanning 37 malicious wheels, hitting bioinformatics, graph-ML, and developer-tooling libraries, with the .pth mechanism fetching and running the same category of Bun-executed JS stealer. Because .pth hooks fire on any Python invocation in that environment — a test run, a linter, a completely unrelated script — the blast radius is wider than a compromised package's own call sites.
What did these campaigns actually steal?
Public reporting across both waves lists a consistent target list: GitHub, npm, PyPI, RubyGems, and JFrog registry tokens; AWS, GCP, and Azure credentials; Kubernetes service-account material; Docker configuration; SSH keys; shell history; and files like .env, .npmrc, and .pypirc. Reporting on the Hades wave also described scraping CI/CD runner process memory for ephemeral secrets that never touch disk, which defeats file-based secrets scanning entirely. The consistent thread is that these are developer-machine and build-pipeline credentials — the kind that, once stolen, let an attacker publish further malicious releases under a legitimate maintainer's identity, exactly the pattern both campaigns used to propagate.
How do you detect a non-Python binary inside a Python package?
Most Python-ecosystem tooling — linters, SAST, even basic malware heuristics — was built to parse .py source, so a compiled binary or a runtime-fetch call sitting next to it is often invisible unless something is specifically looking for it. Practical detection means checking wheel and sdist contents for executable binaries, unusual file extensions, or high-entropy blobs that have no business in a Python distribution; watching for install-time or import-time network calls to binary-hosting infrastructure like GitHub releases; and flagging any .pth file that does more than add a path — since a .pth line starting with import is a startup hook, not metadata. Safeguard's Eagle classifier scores install-script behavior, egress patterns, and credential-harvesting indicators across every PyPI publish and retroactively rescans the historical corpus, which is how a package that looked clean at install time gets re-flagged the moment a new indicator — like a Bun-runtime fetch pattern — is added to the model. The Package Firewall's static behavioral analysis inspects package contents at install time without executing any code, which is exactly the point in the pipeline where a hidden runtime binary or an unexpected .pth payload should be caught before it ever reaches a developer's machine.
What should teams do differently after these incidents?
Pin dependency versions and verify hashes rather than trusting floating ranges, since both compromises relied on victims upgrading automatically within a narrow live window. Audit site-packages for .pth files you didn't put there — they're an underexamined persistence mechanism precisely because they aren't "package code" in the conventional sense. Treat any outbound network call from a Python import path to a binary-release host (GitHub, S3, arbitrary CDNs) as a signal worth alerting on, not routine telemetry. And build re-scanning into your process: the lightning and Hades payloads weren't caught by a one-time install-time check alone — they were surfaced by researchers analyzing artifacts after the fact, which is the same reason retroactive classification against updated indicators matters as much as the initial gate.