- Book: AI Agents Pocket Guide
- Also by me: Database Playbook
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
On April 22, 2026, Socket.dev flagged the first malicious version of @automagik/genie, an npm package published by Namastex Labs, an agentic-AI company. By the time the writeup went up, at least 16 packages across multiple namespaces were compromised, new malicious versions were still being published, and a 1,143-line credential-harvesting script was firing on every install via the postinstall hook. No interaction. No user prompt. Just npm install and the worm was inside.
The campaign — researchers are calling it CanisterSprawl, a CanisterWorm-style worm — is the kind of incident that makes a CISO cancel their weekend. It is also one of three documented npm/PyPI/Docker-Hub supply-chain campaigns that hit between April 21 and 23, 2026, in addition to the Axios npm compromise three weeks earlier where a backdoored release sat live for under three hours and still hit a package with 100M+ weekly downloads.
This post walks through the mechanism, the defensive checklist that would have caught it, and a 20-line script you can wire into CI today.
The mechanism
The CanisterSprawl worm is a textbook example of what an AI-aware supply-chain attack looks like in 2026. Four moving parts:
1. Compromised maintainer. The initial breach was an account takeover. Researchers at Socket and StepSecurity noted strong overlap with the earlier TeamPCP CanisterWorm campaign, which hit Trivy, KICS, LiteLLM, and Telnyx between March 19 and 27, 2026. The attackers know how to phish npm publish tokens.
2. Postinstall trigger. The malicious payload runs from the postinstall lifecycle hook in package.json. Every consumer who runs npm install, npm ci, or any equivalent in a Docker build executes the script, no questions asked. There is no UI. There is no warning. The script fires before your app code ever runs.
3. Credential exfiltration. A 1,143-line script harvests environment variables, ~/.aws/credentials, ~/.npmrc tokens, SSH keys, and GitHub tokens. Stolen data goes to two endpoints: a regular HTTPS webhook and an ICP canister-backed C2, which is harder to seize than a traditional server.
4. Self-propagation. This is the part that turns a breach into a worm. The script checks for an npm publish token. If it finds one, it lists every package the token can publish, increments the patch version on each, injects the same payload, and republishes with --tag latest. Any developer who runs npm install <that-package> without an exact version pin in their lockfile pulls the new infected version and becomes the next propagation vector.
The "AI angle" is not the worm itself. It's that AI-tooling packages — agentic AI libraries, MCP servers, LLM-tooling SDKs — are growing fast, often maintained by small teams, and frequently installed inside CI pipelines that hold cloud credentials. They are exactly the place a supply-chain attacker wants to land.
Why your build is the soft spot
A typical CI runner doing npm install for an agentic-AI service at deploy time has, in its environment, some combination of: an AWS access key, a database password, an OpenAI or Anthropic API key, a GitHub token, an npm publish token if it deploys packages, and a Slack webhook for notifications. A 1,143-line script does not need to be clever. It needs three seconds.
The Axios incident makes the same point with a different vector. Two backdoored versions (axios@1.14.1 and 0.30.4) sat on npm for under three hours on March 31, 2026. They still affected a non-trivial number of CI builds because of how often npm install runs and how few teams pin to exact versions. The WAVESHAPER.V2 RAT was attributed to UNC1069, a North Korea-linked group. Three hours of exposure, global blast radius.
Two different threat actors. Same root cause: trust in npm install is unreasonable, and most build pipelines act as though it isn't.
The defensive checklist
Five controls. Pick all of them. None is sufficient on its own.
1. Lockfile pin with --frozen-lockfile
The single highest-value control. If your package-lock.json pins exact versions and your CI runs npm ci (which fails on lockfile drift) instead of npm install, the worm cannot trick you into pulling a new patch release between the time it republishes and the time the bad version is yanked.
# good
npm ci
# bad
npm install
Most teams "have a lockfile" but run npm install in CI anyway, which silently updates the lockfile when it diverges. npm ci fails the build instead. That failure is a feature.
2. Integrity hash verification
Lockfiles include integrity fields with subresource hashes. Confirm your CI actually verifies them. For Yarn, yarn install --immutable does this. For pnpm, pnpm install --frozen-lockfile. For npm, npm ci does. The Axios attackers republished new tarballs under the same minor versions, but a pinned integrity hash mismatch would have failed the install.
3. Registry scoping
Use a private registry mirror or proxy (Verdaccio, Nexus, JFrog Artifactory) that pulls from npmjs.org only when you ask it to. This gives you a single point where you can:
- Quarantine new versions of critical packages until a 24-hour soak window passes.
- Block packages with a
postinstallhook unless explicitly allowlisted. - Audit who is pulling what.
A 24-hour quarantine on new releases would have caught both Axios and CanisterSprawl. The bad versions were yanked within hours.
4. Disable lifecycle scripts where possible
You can run npm install --ignore-scripts to skip postinstall, preinstall, and friends. Some packages legitimately need build steps (node-gyp, native bindings), so a global ignore breaks things. The pragmatic version: a CI step that installs with --ignore-scripts first, runs a static scan over the dependency tree, then runs the real install with scripts enabled. This catches the obvious cases.
5. Runtime tool-call audit for agent code
Specific to agentic AI codebases. If your service is an LLM agent that calls tools, ensure the tool-call audit log captures every child_process.exec, every network request, and every filesystem write. The same supply-chain risk applies to MCP servers and agent tool packages — a compromised tool definition is a compromised tool, and it runs in the same process as your secrets.
A simple shape: pipe every tool-call invocation through a wrapper that records {tool_name, args, timestamp, caller} to an append-only audit log. Periodically diff that log against a baseline.
A 20-line audit script
The script below does one specific thing: scans an npm install event for the most common red flags from the CanisterSprawl playbook. Wire it into CI before the actual install runs.
#!/usr/bin/env bash
# scan-install.sh — flag suspicious npm install events
set -euo pipefail
PKG_JSON="${1:-package.json}"
LOCK="${2:-package-lock.json}"
ALERTS=0
flag() { echo "ALERT: $1"; ALERTS=$((ALERTS + 1)); }
# 1. Lockfile must exist and be up to date
[ -f "$LOCK" ] || flag "no lockfile found"
# 2. Detect packages with postinstall scripts
node -e '
const lock = require(process.argv[1]);
const pkgs = lock.packages || {};
for (const [path, p] of Object.entries(pkgs)) {
if (p.scripts && (p.scripts.postinstall ||
p.scripts.preinstall || p.scripts.install)) {
console.log("HOOK:" + path);
}
}
' "$LOCK" | while read line; do
flag "$line"
done
# 3. Recently published versions are suspicious
npm outdated --json --all 2>/dev/null \
| node -e '
let buf=""; process.stdin.on("data",d=>buf+=d);
process.stdin.on("end",()=>{
if(!buf) return;
const o = JSON.parse(buf);
for (const [name, info] of Object.entries(o)) {
if (info.latest === info.wanted) continue;
console.log("UPDATE:" + name + ":" + info.latest);
}
});'
# 4. Block install if any alert tripped without sign-off
if [ "$ALERTS" -gt 0 ] && [ -z "${ALLOW_RISKY:-}" ]; then
echo "Aborting. Set ALLOW_RISKY=1 after manual review."
exit 1
fi
Three things this catches and one thing it does not. It catches packages with lifecycle hooks (the CanisterSprawl entry point), missing or stale lockfiles, and version drift since your last known-good install. It does not catch a backdoor in a package that has always had a postinstall hook for legitimate reasons (node-gyp, native modules). For those, the answer is the registry quarantine in step 3 of the checklist, not a script.
Run this in CI before npm ci. Tune the allowlist for packages that have a justified postinstall. The signal-to-noise is reasonable on a typical Node service.
The longer view
Two patterns are showing up across the 2026 supply-chain incidents.
The first is that AI-tooling packages are now first-class targets. The TeamPCP campaign hit LiteLLM on PyPI. CanisterSprawl hit an agentic-AI company's packages. There is also the prt-scan campaign Wiz disclosed that used AI-generated GitHub Actions exploits since March 11, 2026. Attackers are noticing the same thing the rest of us are: AI-coding teams ship fast, hold a lot of secrets, and tend to run with broad permissions.
The second is that exfiltration infrastructure is getting harder to take down. ICP canisters, Discord webhooks, abandoned Cloudflare Workers — the C2 endpoint of choice keeps shifting toward platforms where takedowns are slower or impossible. The defender's only durable lever is what crosses the egress boundary in the first place.
That puts the responsibility back on the install pipeline. If the secrets aren't in the build environment, the script can't exfiltrate them. If the install fails on lockfile drift, the worm can't propagate from your machine. If the registry quarantine soaks new releases for 24 hours, the bad versions are yanked before they reach you. None of these are exotic. All of them are off by default.
Run the checklist this week
Three things to do before the next deploy:
- Switch your CI from
npm installtonpm ci. Audit any failures. They are signal, not noise. - Stand up a registry mirror or turn on the one your enterprise license already includes. Configure a soak window for new versions of the top 50 packages your fleet depends on.
- Wire the script above (or your equivalent) into the build, gated by an environment variable so engineers can override after manual review.
The next CanisterSprawl is already being staged. The Axios attackers had a known toolkit. The CanisterWorm operators have been iterating since at least March. The next campaign is not a matter of if, and the controls above are the difference between a Slack alert and a security review.
If this was useful
The AI Agents Pocket Guide covers the runtime side of this — how to instrument an agent's tool calls so a compromised tool definition shows up in your audit log instead of disappearing into the noise, and how to design the guardrails that survive a malicious dependency landing in your tool surface. The Database Playbook covers the same shape of question for the data layer: where credentials should live, what the blast radius of a leaked CI secret looks like, and how to design schemas and access patterns that limit damage when something gets out.


Top comments (0)