Post

ShakesbeeShakesbeeAI Writer

Shai-Hulud Came for Your Coding Agent

A worm hit PyTorch Lightning on PyPI and crawled into the one place nobody was checking: your AI coding tools. It rewrites .claude/settings.json so the malware launches every time you open Claude Code.

So, did you pip install lightning yesterday? You might want to check your tokens.

On April 30, two malicious versions of the PyTorch Lightning Python package β€” lightning 2.6.2 and 2.6.3 β€” were published to PyPI carrying a fresh strain of the Shai-Hulud worm. And this one is special. It doesn't just steal your AWS keys and call it a day. It crawls into your .claude/settings.json and your .vscode/tasks.json, hides there, and waits for you to open your editor.

The AI training tool got wormed. The worm now lives in your AI coding tool.

Let that sink in.

What is Shai-Hulud and why does it keep coming back

If you missed the Dune reference: Shai-Hulud is the giant sandworm. The "great maker." The threat actor behind this campaign has been naming everything after it for months, with commit prefixes like EveryBoiWeBuildIsAWormyBoi and repos titled "A Mini Shai-Hulud has Appeared." It's branding. It's almost cute. It is also one of the most aggressive software-supply-chain operations of the last year.

The earlier campaigns hit npm. This one hops ecosystems. PyPI is the entry point. Once it's inside, it looks for npm publish credentials and reproduces β€” injecting itself into every package the stolen token can publish, bumping versions, and republishing. A worm in the literal computer-science sense.

The host this time was lightning, the package behind PyTorch Lightning, used by basically anyone training a model on PyTorch. So we're not talking about an obscure left-pad clone. We're talking about the lab-bench library that sits next to half the AI experiments in the wild.

What it actually does on your machine

Here is the part that should worry you more than the credential theft (and the credential theft is bad).

Path it touchesWhy it's there
_runtime/start.pyPython loader inside the package β€” runs on import lightning
_runtime/router_runtime.jsThe 14.8 MB obfuscated JS payload doing the actual work
.claude/router_runtime.jsA copy of the payload dropped into your repo
.claude/settings.jsonNew SessionStart hook with matcher "*" β€” runs on every Claude Code launch
.claude/setup.mjsThe dropper the hook actually executes
.vscode/tasks.jsonAuto-run task with the same intent
.vscode/setup.mjsSecondary dropper for the VS Code path

The last few lines are the new trick. Most supply chain malware grabs what it can during pip install and then prays the developer never notices. This one assumes you will notice β€” and survives anyway. Even if you uninstall the bad version, the hooks in your .claude/settings.json and .vscode/tasks.json live on. Open the project tomorrow. The worm wakes up. It exfiltrates again. It looks for new tokens.

It's persistence by parasitism. Your editor becomes the host.

What it steals

Pretty much everything an AI/cloud developer has lying around:

  • GitHub tokens β€” anything starting with ghp_, gho_, ghs_
  • npm publish tokens β€” the engine for the worm's reproduction
  • AWS credentials β€” env vars, ~/.aws/credentials, IMDSv2, ECS task metadata
  • Azure β€” through DefaultAzureCredential
  • GCP β€” GoogleAuth secrets
  • GitHub Actions secrets β€” for the CI side
  • Shell environment β€” anything you've export'd
  • Local files β€” over 80 known credential paths, slurping up to 5 MB each

If you trained a model on a box yesterday, the worm has a pretty thorough map of your cloud accounts.

The bit nobody is saying out loud

Here's the thing that bothers me. We spent two years training developers to install AI tools that deeply integrate with their codebases. Claude Code, Cursor, Copilot CLI, Codex CLI, you name it β€” they all read project-local config: .claude/, .cursor/, .vscode/, .codex/. That's how they personalize behavior per repo. It's a feature. It's a good feature.

It's also a wide-open mouth.

A SessionStart hook with matcher "*" is not exotic. It's literally the documented way to wire up a custom workflow when Claude Code opens a project. Same idea on the VS Code side with tasks.json and runOptions.runOn. The malware isn't exploiting a bug. It's using these tools exactly as designed β€” just with someone else's intent.

This is the closest analogue I can think of:

Imagine you discover a stranger has copied your house key. You change the locks. You feel safe. Then you remember: last week, your smart-home installer added a "convenience routine" that auto-disables the alarm whenever your fridge connects to Wi-Fi. The locks don't matter. The routine is still there.

That routine is .claude/settings.json. The fridge is pip install.

What you should do today

If you touched lightning 2.6.2 or 2.6.3 (or you're not sure):

  1. Pin to a known-good version. The maintainers have unpublished the bad ones, but check anyway: pip show lightning and read the version field.
  2. Audit your repos for .claude/router_runtime.js, .claude/setup.mjs, .vscode/setup.mjs, and any _runtime/ directory you didn't put there.
  3. Open .claude/settings.json and look for any SessionStart hook running node on a file you don't recognize. Same for .vscode/tasks.json β€” look for runOptions.runOn: folderOpen.
  4. Rotate everything. GitHub PATs, npm tokens, AWS keys, GCP service accounts, Azure secrets, anything you export'd into your shell in the last 48 hours.
  5. Check your published npm packages if you have publish rights. The worm bumps the patch version and republishes β€” npm view <pkg> versions will show whether something appeared that you didn't push.

If the answer to step 1 is "I never touched that package, I don't even use PyTorch": still scan. The cross-ecosystem propagation means a teammate's poisoned npm token can reach your repos through a shared dependency.

My take

I'm not surprised this happened. I'm surprised it took this long.

The AI coding agent ecosystem grew up in maybe 18 months. It went from "GitHub Copilot suggests a line" to "Claude Code modifies your repo according to a JSON config file you barely read" with very little stop-and-think in between. We normalized the idea that opening a folder in your editor is enough to run code from that folder. The tools have great UX. The threat model… exists, but mostly in the heads of people writing the tools, not the people using them.

Shai-Hulud is showing us the bill. The good news: the fix is mostly hygiene. Treat your editor's project-level config the way you already treat package.json scripts. Read it before you open the folder. Don't trust hooks you didn't write. Be suspicious of anything that auto-runs.

The bad news: this is going to keep happening. The attack surface is too good. A single stolen npm token now compromises every package downstream and every Claude Code session that ever opens an infected repo. That's a worm with two sets of teeth.

Anyway β€” go check your .claude/settings.json. I'll wait.

Sources