PyTorch Lightning, Mini Shai-Hulud, and Malware That Signs Commits as Claude Code

Written by the Rafter Team

On April 30, 2026, two malicious versions of the PyPI package lightning — versions 2.6.2 and 2.6.3 — were published with credential-stealing code that activates the moment the package is imported. The package is the official PyPI distribution of PyTorch Lightning, an AI-training library with more than 31,000 GitHub stars. PyPI has quarantined the project. The maintainers published security advisory GHSA-w37p-236h-pfx3 the same day, telling users to assume the environment is compromised and to rotate every reachable credential.
The technical chain is familiar — import-time hook, JavaScript-runtime dropper, obfuscated multi-megabyte payload, four-channel exfiltration, worm propagation through stolen GitHub tokens. The choice that distinguishes this campaign from previous Mini Shai-Hulud activity is who the malware claims to be: every poisoned commit the worm pushes to victim repositories is authored under a hardcoded identity designed to impersonate Anthropic's Claude Code.
If lightning was installed on any developer machine or CI runner between April 30, 2026 and now, treat the host as compromised. Rotate every npm, GitHub, and cloud-provider token reachable from that environment today, not on the next ticket-cleanup day. Pin to lightning==2.6.1. The malicious releases executed at import time, and credential theft is the first thing they did.
The chain, end to end
Both compromised releases include a hidden _runtime/ directory with a Python entry point, start.py, that fires on import lightning. start.py is small. Its purpose is to invoke setup.mjs, a JavaScript dropper that downloads the Bun JavaScript runtime — bun-v1.3.13 — from GitHub releases if it isn't already present locally. Bun runs on every modern developer platform: Linux x64/arm64/musl, macOS x64/arm64, Windows x64/arm64.
Once Bun is on disk, the dropper executes router_runtime.js, an obfuscated payload that sources size at roughly 11 to 15 megabytes. That payload is the campaign's actual workload, and it does two things in sequence: harvest credentials, then propagate.
Credential harvest
The collector is comprehensive. It enumerates more than 80 known credential-file paths searching for ghp_, gho_, and npm_ token prefixes. It reads up to 5 MB per file. It collects every process environment variable. It pulls AWS credentials from the IMDSv2 metadata service, ECS task metadata, Secrets Manager, and SSM Parameter Store. It pulls Azure secrets from Key Vault. It pulls GCP secrets from Secret Manager.
If the package is being imported inside a GitHub Actions workflow, the collector scrapes the workflow's secrets context — including values explicitly marked isSecret: true. That includes the values your security team specifically marked as sensitive on the assumption they would be redacted.
Exfiltration
Four channels run in parallel. Redundancy is the design goal:
- HTTPS POST to an attacker C2 endpoint on port 443 — the boring, high-bandwidth path.
- GitHub commit-search dead drop, where stolen secrets are encoded into commit messages prefixed
EveryBoiWeBuildIsAWormyBoiand pushed to attacker-owned repos. The attacker retrieves them later via GitHub's commit-search API. - Public attacker repositories with Dune-themed names and the description "A Mini Shai-Hulud has Appeared" — both a flex and a backup channel.
- Direct push back into the victim's own repositories when a valid GitHub token is in hand.
That last channel is where the worm propagation lives.
Propagation: the part that matters
Stolen GitHub tokens are validated against the GitHub API. For each accessible repository, the worm injects its payload into up to 50 branches. The persistence files dropped during this step are the heart of the campaign's tradecraft.
Files dropped per victim repo:
.claude/router_runtime.js
.claude/settings.json
.claude/setup.mjs
.vscode/tasks.json
.vscode/setup.mjs
.github/workflows/Formatter.yml
Every poisoned commit is authored under a hardcoded Git identity designed to impersonate Anthropic's Claude Code. To anyone scrolling git log on the victim repo, "claude-code authored this" reads like a routine entry from a teammate's coding agent. The dropped files mimic editor and assistant configuration — exactly the kind of low-attention surface that reviewers tend to skim. The Actions workflow is named Formatter, the most boring possible name for a workflow file.
The Claude Code disguise, deconstructed
Read the persistence pattern as a series of four deliberate camouflage choices.
Choice one: the package. lightning is the kind of dependency every AI-training pipeline already pulls. Adding it to a requirements.txt does not raise eyebrows. Updating it raises fewer. The compromise targets a top-1% PyPI package by stars in the AI/ML space because that maximally minimizes the "is this dependency unusual?" signal.
Choice two: the runtime. Bun is a legitimate, cross-platform JavaScript runtime that downloads cleanly from GitHub's own release infrastructure. Using it as the payload runtime is a defense-evasion choice — it does not look out of place on a modern developer machine, the download URL is GitHub-signed, and the binary itself is benign. The payload's malicious logic lives in the JavaScript Bun runs, not in Bun.
Choice three: the persistence files. .claude/ and .vscode/ directories are normal in modern AI-assisted dev environments. A diff that adds files under either is not surprising. Most repos already have at least one. A scanner configured to flag every new .vscode/tasks.json is a scanner that's noisy half the time and useless the other half — and the attacker is counting on that signal-to-noise ratio.
Choice four: the author identity. This is the choice that elevates the campaign from "another postinstall hook" to "preview of the next phase of supply-chain malware."
Claude Code commits as a known authored identity in repositories where developers use it as their coding assistant. Spoofing that identity gives the attacker the highest-fidelity disguise available: in repos where Claude Code is already in use, the spoofed commits land in a stream of legitimate Claude Code commits and are functionally invisible. In repos where Claude Code is not in use, the spoofed commits look like a teammate experimenting with the tool — the kind of thing a reviewer might shrug at rather than escalate.
Either way, no human reflexively double-checks the git log author column.
Where this fits in the pattern
The PyTorch Lightning compromise is not isolated.
CanisterSprawl (April 21, npm) used the same install-time, multi-channel-exfil shape against AI-agent tooling. Sandworm-mode (February, npm) wormed through the AI-coding-tool ecosystem and rewrote MCP configs. Trivy / TeamPCP (March, GitHub Actions) compromised the security tooling itself. Each campaign hit a different layer of the AI-developer-tool supply chain. The PyTorch Lightning incident hits the training-library layer, with the same shape and a new disguise.
The connective tissue is the same actor lineage. Researchers attribute Mini Shai-Hulud to the same group behind TeamPCP / CanisterWorm activity, based on shared payload patterns, GitHub-based exfil, and credential-harvest tradecraft. The lightning incident is a sibling of the intercom-client@7.0.4 npm compromise published in the same window.
What to do today
Pin and block. PyTorch Lightning 2.6.1 is the last known-clean release. Pin to it explicitly. Add 2.6.2 and 2.6.3 to your dependency-policy denylist; PyPI's quarantine helps for fresh installs but does nothing for cached lockfiles, vendored copies, or build artifacts that already include the compromised version.
Treat post-April-30 lightning installs as compromise events. If any developer machine or CI runner ran pip install lightning (or lightning was upgraded as a transitive dependency) between April 30 and now, rotate npm, GitHub, and cloud-provider tokens reachable from that environment today. Do not wait for the maintainers' post-mortem.
Grep for the IOCs. The persistence pattern is concrete. Run find across your repos for the file list above. Audit recent commits authored under a Claude Code identity, especially in repositories where your team does not actually use Claude Code. Audit .github/workflows/ for any file named Formatter.yml.
Re-evaluate post-install-hook policy. Python installs do not have npm's ignore-scripts switch, but the same principle applies: import-time side effects are functionally equivalent to install-time hooks once a Python package is in your environment. Lockfile-pinning, hash-verification (pip install --require-hashes), and dependency review on every PR are all backstops for the gap between a poisoned release and an advisory.
Use a scanner that surfaces this on push. Rafter's Code Analysis Engine flags known-vulnerable dependency versions on every push once advisory data is available. That closes the window between the disclosure and the next CI run. It does not close the window between the attacker publishing 2.6.2 and the maintainer publishing GHSA-w37p-236h-pfx3 — pinning and grepping cover that window today.
Closing on the disguise
A year ago, a malicious commit authored by dependabot[bot] would have been the canonical "trust the bot identity" attack. Today, it is claude-code — the Git identity of an AI assistant that a non-trivial fraction of senior engineers actively use, and that the rest of them have at least heard of.
The lightning campaign is a preview of what supply-chain attackers will keep doing as AI-assisted development becomes the default. They will impersonate the assistants. They will mimic the assistants' config files. They will sign commits with the assistants' names. The trust those assistants have built — earned, in many cases — will be the cover.
Trust signals are getting cheaper to forge faster than they are getting harder to verify. If your malware can sign commits in your AI assistant's name, the assistant is now a stolen identity, not a tool.
Further reading
- Three Supply Chains, One Trust Relationship — the same install-time, multi-channel-exfil shape against AI-agent tooling on npm.
- Sandworm-mode: the npm worm that injected MCP servers — the AI-era variant where malware weaponizes the assistant's config to phone home.
- Trivy / TeamPCP supply-chain compromise — same actor lineage, against security tooling rather than ML tooling.
- A Real Email From Robinhood Carrying Real Phishing — what happens when the trust signal a defender uses is exactly the signal the attacker mimics.
Sources
- PyTorch Lightning maintainers — GHSA-w37p-236h-pfx3
- Semgrep blog — Shai-Hulud Themed Malware Found in the PyTorch Lightning AI Training Library
- The Hacker News — PyTorch Lightning and Intercom-client Hit in Supply Chain Attacks
- Cybersecurity News — Popular Python Package lightning Hacked in Supply Chain Attack