All posts
Security Supply Chain May 2026

The PyTorch Lightning Compromise: When the AI Framework Is the Vector

On April 30, 2026, the lightning package on PyPI - the runtime for PyTorch Lightning - was live for 42 minutes carrying a credential-stealing worm. Eight days after Bitwarden CLI. Same campaign. New targets: ML training pipelines. New capability: persistence inside Claude Code and VS Code that survives removing the compromised package.

42 min
Window before quarantine
3 registries
PyPI, npm, and Packagist hit
8 days
After Bitwarden CLI compromise

We wrote about the Bitwarden CLI supply chain attack eight days ago. That one was npm. This one is PyPI - and the same campaign, on the same day, also hit npm and Packagist.

On April 30, 2026, versions 2.6.2 and 2.6.3 of the lightning package - the PyPI distribution for PyTorch Lightning - were published carrying a credential-stealing worm. Socket detected both malicious versions eighteen minutes after publication. PyPI quarantined them within 42 minutes. That is a fast response by any measure. It is also 42 minutes for a package that sees hundreds of thousands of downloads daily.

The campaign is the same one. Analysts are calling this wave “Mini Shai-Hulud” - assessed as connected to the full Shai-Hulud operations that hit Bitwarden CLI, Trivy, and others, sharing the same Bun-based delivery, the same Dune-themed indicators, and the same worm propagation mechanism. The target is different.

What happened in 42 minutes

The attackers obtained PyPI publishing credentials for the lightning package directly, bypassing the project’s GitHub source control entirely. Both malicious versions appeared on April 30, 2026, with 2.6.3 published 13 minutes after 2.6.2 - an active operator incrementing versions to maximize reach.

A pl-ghost account with write access to the project’s GitHub organization showed suspicious behavior throughout the window: rapid branch creation and deletion, disclosure issues closed within a minute of being filed, a “SILENCE DEVELOPER” meme posted in response to community warnings. Socket assessed the pl-ghost account as compromised rather than a rogue insider - the branch creation-and-deletion patterns match the Shai-Hulud worm’s write-access probing behavior seen in prior incidents.

Community members noticed, filed issues, and pushed the response. Lightning AI quarantined the malicious versions 42 minutes after they appeared. That is the headline number. What it does not tell you is whether your specific pipeline hit the window.

Why PyTorch Lightning

PyTorch Lightning is an open-source deep learning framework with over 31,100 GitHub stars. ML and data science teams use it to structure training runs. It is not a fringe tool.

ML training environments are credential-dense. A typical training run has access to cloud compute credentials for the GPU cluster, object storage credentials for training data and model checkpoints, API keys for experiment tracking services like Weights and Biases or Hugging Face, and CI/CD secrets from whatever pipeline runs the jobs. The malware’s target list reflects this precisely: over 80 filesystem paths covering GitHub tokens, npm tokens, SSH keys, AWS and GCP and Azure credentials, Kubernetes secrets, and Docker credentials, plus cryptocurrency wallets including Bitcoin, Litecoin, Monero, Dogecoin, Exodus, and Ledger. It also hits cloud metadata endpoints directly - AWS IMDS at 169.254.169.254, Azure token caches, GCP OAuth introspection endpoints. The worm searches exactly where ML practitioners store credentials.

What the malware does

The payload executes automatically on module import. Not on install - on import. A modified __init__.py spawns a background daemon thread the moment you run import lightning anywhere in your code. The thread suppresses its output, so nothing surfaces in your terminal or logs.

The thread runs _runtime/start.py, which downloads Bun JavaScript runtime v1.3.13 and executes router_runtime.js - an 11 MB obfuscated JavaScript payload using hex-encoded string array rotation and secondary AES decryption. Socket identified it through behavioral analysis rather than signature matching, which is why the 18-minute detection time matters: the obfuscation was designed to defeat static tools.

Data leaves through four parallel channels: HTTPS POST to a command-and-control server, GitHub commit search dead-drops using messages prefixed “EveryBoiWeBuildIsAWormyBoi”, public GitHub repositories created under victim accounts with the description “A Mini Shai-Hulud has Appeared”, and direct commits to victim repositories using stolen tokens.

One entry point, three registries

Previous Shai-Hulud waves used npm as both entry point and propagation mechanism. This campaign used PyPI to deliver the worm and then spread across every registry it could reach.

If the payload discovers npm publishing credentials, it injects a setup.mjs dropper into every package that token can publish to, increments the patch version, and republishes. The mechanism repackages local .tgz tarballs so that when the developer next publishes from their local environment, the tampered version is newer than the registry copy and gets accepted as a legitimate update. A compromised ML training environment can seed a JavaScript supply chain attack with no connection to ML at all.

The April 30 operation did not stop there. Intercom-client version 7.0.4 on npm was compromised in the same campaign, using a now-deleted branch to trigger automated CI workflows. Intercom and intercom-php on Packagist were also hit via Composer plugin execution. The same worm, on the same day, moving through Python, JavaScript, and PHP package distribution simultaneously.

The developer tools angle

There is something in this payload that was not in the Bitwarden CLI attack.

If the payload finds Claude Code installed, it writes a SessionStart hook to .claude/settings.json matching all sessions, pointing to a dropper at .vscode/setup.mjs. If it finds VS Code, it creates a runOn: folderOpen task in .vscode/tasks.json executing .claude/setup.mjs whenever you open a project folder. Either way, the payload re-runs every time you open your editor.

Removing the compromised package does not clear these. The persistence survives the package removal. The credential harvest keeps running, every editor launch, until you find and delete the injected entries.

The repository poisoning adds another layer. The worm commits to victim repositories using the spoofed identity claude@users.noreply.github.com, impersonating Anthropic’s tooling to make its commits look like AI-assisted development work. Commits from that address that you did not author are a direct indicator of compromise from this campaign.

The irony in the Bitwarden CLI attack was that a password manager’s CLI briefly became a credential thief. The irony here is sharper. The AI coding tools you use every day become the worm’s persistence mechanism, and then it disguises its commits as your AI-assisted work. The trust you extend to developer tooling is precisely what gets turned against you.

Eight days

The Bitwarden CLI compromise was April 22. This was April 30. Eight days.

We noted in the Bitwarden post that the direction across Shai-Hulud waves is toward higher-privilege targets with smaller blast radii. The Bitwarden CLI attack targeted a specific security tool in CI/CD pipelines. This attack targeted ML training infrastructure - environments with denser credential access and, generally, less security scrutiny than standard web development pipelines.

A Team PCP-linked onion site claimed involvement in this operation, with references to LAPSUS$ and Checkmarx leak infrastructure - the same C2 pattern that appeared in the Bitwarden CLI analysis. Attribution is still contested across the broader campaign, but the target selection logic is consistent across both attacks: go where the high-value credentials accumulate, go where the tools carry elevated trust and low scrutiny.

What you can actually do

Version 2.6.1 is the last clean version. If you installed 2.6.2 or 2.6.3, downgrade: pip install lightning==2.6.1.

If either compromised version was installed in any environment, rotate every credential that environment could reach. GitHub tokens, npm tokens, AWS keys, GCP credentials, SSH keys - treat any credential accessible from that machine as potentially exposed.

Check .claude/settings.json for unexpected SessionStart hooks. Check .vscode/tasks.json for unexpected runOn: folderOpen tasks. Neither file changes on its own. An unexpected entry is worth investigating.

Audit your git history for commits from claude@users.noreply.github.com on any repository accessible from the affected environment. The worm uses that spoofed identity specifically to blend in.

If you maintain npm packages and your npm token was accessible from the affected environment, check your npm publish history for unexpected version bumps. The worm injects into local .tgz files and republishes them from your local environment, not from infrastructure it controls. An unexpected patch version on one of your packages is a direct indicator.

The 42-minute quarantine was fast. Whether it was fast enough for your specific environment depends on when your CI/CD last ran pip install. A pipeline that updates dependencies daily could have hit the window on April 30. Knowing which one you are is the first step.


We also wrote about the Trivy supply chain attack and the Bitwarden CLI compromise when they happened, because we run these tools in our infrastructure too. If you are working through whether you were affected or want to compare notes on what we’ve seen, we’re easy to reach.

Sources: