Architecture28 min read

The Worm That Ate the Supply Chain: What Mini Shai-Hulud Reveals About Every Security Control You’re Trusting

How a self-propagating npm/PyPI worm defeated SLSA Build Level 3 provenance attestation, breached OpenAI and Mistral AI, open-sourced its own code, and what a layered defense posture actually looks like against this class of attack.

How a self-propagating npm/PyPI worm defeated SLSA Build Level 3 provenance attestation, breached OpenAI and Mistral AI, open-sourced its own code, and what a layered defense posture actually looks like against this class of attack.

This pairs with immutable releases on Arweave/IPFS, macOS Seatbelt containment, and Hardened Agentic Stack Part 12 on Cosign, digests, and skill vetting. The Layer 0 release posture and sandbox module live in ClawQL docs.

What Happened

Between September 2025 and May 2026, a threat group called TeamPCP ran a coordinated supply chain campaign called Shai-Hulud — named, with some irony, after the giant sandworms in Frank Herbert’s Dune novels. The campaign produced four generations of an increasingly sophisticated self-propagating worm targeting npm and PyPI.

The fourth generation, Mini Shai-Hulud, is the one that broke things open. Not just in scale — over 170 packages compromised, 518 million cumulative weekly downloads, OpenAI, Mistral AI, the European Commission, and GitHub all breached — but in what it proved was possible.

Mini Shai-Hulud defeated SLSA Build Level 3 provenance attestation.

If you don’t know what that means, you will by the end of this post. If you do know what that means, you understand why it matters.

How the Worm Actually Works

The core mechanism is elegant in a deeply unpleasant way. It’s a self-propagating worm — not in the old network-scanning sense, but in a supply chain sense. Each infection creates new vectors.

Step 1: Initial compromise. The worm finds a way into a package maintainer’s environment — through a phishing campaign, a credential dump from a prior breach, or in later generations, by compromising a package that developer already has installed. This is the bootstrap problem for any supply chain attack, and TeamPCP addressed it by first targeting security tooling: their March 2026 wave hit the Aqua Security Trivy scanner, meaning the tool many teams use to detect supply chain attacks was itself a distribution vector.

Step 2: Credential harvesting. Once the malware executes — in Mini Shai-Hulud’s case, via a preinstall hook that runs before dependency installation completes — it sweeps the system for credentials. Over 80 environment variables and filesystem paths are targeted: ~/.npmrc, ~/.aws/credentials, ~/.ssh/, ~/.kube/config, ~/.docker/config.json, GitHub personal access tokens, HashiCorp Vault tokens, CI/CD secrets. Everything.

Step 3: Autonomous propagation. The harvested credentials are used to publish poisoned versions of every package the compromised account has publish access to. No further attacker input required. Each new infected package runs the same credential harvesting and propagation logic when installed — exponential spread through the ecosystem.

Step 4: Triple-redundant exfiltration. Stolen credentials leave through three channels simultaneously: a dedicated C2 server, the Session decentralized messenger network, and GitHub API dead drops. Blocking one channel doesn’t stop exfiltration.

Step 5: Persistence. A daemon called gh-token-monitor installs via systemd (Linux) or launchctl (macOS). It polls GitHub every 60 seconds. If it detects that the stolen token has been revoked, it triggers recursive file deletion before the credential rotation can take full effect. This is why Tenable’s remediation guidance says: find and remove the daemon before rotating credentials, not after.

The TanStack wave added a sixth step that changes what security controls you can trust.

Lesson: a supply chain worm succeeds when credential harvest, publish access, and persistence share one install event. Break any of those three and propagation dies; leave all three intact and exponential spread is the default.

The SLSA Defeat: Why This Changes Everything

SLSA (Supply-chain Levels for Software Artifacts) is a framework for verifying that software was built from a trusted source through a trusted process. Build Level 3 — the highest practical tier — requires cryptographic provenance generated by the build system itself, verified through Sigstore. When you run npm audit signatures on a Level 3-attested package, you’re checking that the package was built exactly as the maintainer intended, by the maintainer’s own pipeline.

Here’s what TeamPCP did in the TanStack wave (CVE-2026-45321):

They created a fork of the TanStack/router repository under a renamed account to avoid immediate detection. They opened a pull request that triggered a pull_request_target workflow. This workflow — a common pattern in GitHub Actions for PRs from forks — executes code from the PR in the base repository’s trusted context, not the fork’s context. This is a known footgun in GitHub Actions configuration, but it’s also widely deployed because it’s the only practical way to give fork PRs access to certain secrets.

With code executing in the trusted context, they poisoned the GitHub Actions cache with malicious binaries. Later, when legitimate maintainer pull requests were merged, the release workflow restored the poisoned cache. Their code then extracted OIDC tokens directly from the runner’s process memory and exchanged them with npm’s federation endpoint for full publish credentials.

Result: 84 malicious package versions published across 42 TanStack packages in under six minutes, all carrying valid SLSA Build Level 3 provenance attestations from Sigstore.

Valid attestations. The build pipeline was legitimate. The provenance was real. The process was trusted. The code was malicious.

This is the finding that matters: SLSA provenance attestation verifies that the build process was legitimate. It cannot verify that the code going into that process was clean. When an attacker hijacks the legitimate pipeline itself rather than publishing from an unauthorized account, provenance attestation becomes evidence of nothing.

This doesn’t mean SLSA is useless. It remains a valuable signal. It means SLSA cannot be a sufficient signal. Any security control that verifies process integrity without independently verifying code integrity is vulnerable to the same class of attack.

The organizations that assumed SLSA attestation was their supply chain security story discovered in May 2026 that they were wrong.

Lesson: process attestation without content pinning is a certificate that the wrong binary came from the right factory. Layer content-addressed hashes on top — or treat “signed by CI” as a soft signal, not a gate.

The Open-Sourcing

On May 12, 2026, TeamPCP published the Shai-Hulud worm source code on GitHub under an MIT License with the message: “Shai-Hulud: Open Sourcing The Carnage.” They simultaneously announced a $1,000 contest on BreachForums for the largest supply chain attack using their code.

Four copycat campaigns appeared within weeks using modified versions of the code. A rival worm called PCPJack appeared that evicts TeamPCP infections and steals credentials independently — competing malware operating in the same ecosystem.

The security landscape for npm and PyPI is now one where the attack tooling is documented, open-source, community-improvable, and already copied. The original Shai-Hulud worm from September 2025 was “the first self-replicating malware observed in the npm ecosystem.” As of July 2026, there are at least five known variants from multiple threat actors.

Mapping a Layered Defense Against Each Attack Vector

Let’s go through the Shai-Hulud attack chain layer by layer and be specific about what stops it, what limits the damage, and what doesn’t help — using the posture ClawQL ships (Seatbelt, Layer 0 manifests, Vault-backed secrets, Falco/Tetragon) as a concrete worked example of the pattern, not as the only way to implement it.

Vector 1: Compromised Package Installation

The attack: Developer or CI/CD runner installs a package with a malicious preinstall hook. Hook executes during installation, harvests credentials, propagates.

What helps:

--ignore-scripts as the default for npm installs with explicit allowlisting for trusted lifecycle hooks. This is operational hygiene, not a product feature — and it’s the first line of defense any npm-based toolchain should document explicitly.

Kernel-level containment (Seatbelt / clawql-sandbox). A preinstall hook executing under sandbox-exec with a restrictive Seatbelt profile cannot read ~/.npmrc, ~/.aws/credentials, ~/.ssh/, or ~/.kube/config. The credential harvesting step — the one that makes the worm self-propagating — fails because the kernel denies the reads before they complete.

This is the same containment that stops the Matt Shumer class of incident, applied one layer earlier. The profile:

(deny file-read*
  (subpath (param "HOME_SSH"))
  (subpath (param "HOME_AWS"))
  (subpath (param "HOME_CONFIG"))
  (subpath (param "HOME_NPMRC")))

A preinstall hook running under this profile tries to read ~/.npmrc for npm tokens. The kernel returns EPERM. The hook fails to harvest the token. The worm doesn’t propagate from this machine. The containment isn’t perfect — the malicious code still executed, and the infection occurred — but the propagation vector is cut.

Tetragon on the enterprise side enforces the same constraint at the kernel level for containerized workloads. A preinstall hook attempting to exec curl or wget to reach C2 infrastructure fires the Tetragon policy and kills the process before the outbound connection completes.

Vector 2: CI/CD Pipeline Compromise

The attack: Pull request triggers pull_request_target workflow. Code from PR fork executes in trusted context. GitHub Actions cache is poisoned. OIDC tokens are extracted from runner process memory.

What helps:

A Layer 0 release manifest is the direct answer to the SLSA defeat. Because SLSA provenance verification proved insufficient in the TanStack wave, a release posture that doesn’t rely on provenance attestation as a sufficient control adds:

Content-addressed artifacts on Arweave. The manifest records SHA-256 hashes of every artifact in the release bundle. These hashes are written to Arweave — permanent, content-addressed storage where the transaction ID is a cryptographic commitment to the content. An artifact whose hash doesn’t match the manifest hash has been tampered with, regardless of what any attestation says about the build process.

The SLSA defeat works by hijacking the legitimate pipeline so the build process produces malicious output with valid process attestations. Content hashes catch this: the hash of the malicious binary won’t match the hash of the clean binary that was expected. Provenance says “this came from the right pipeline.” Content hashes say “this is the exact thing we published.”

CycloneDX SBOM per build. Every release generates a CycloneDX SBOM that records every dependency, its version, and its hash. This is the bill of materials that makes “which builds included a compromised package” a query rather than an investigation.

Cosign image signing with multiple required signatures. The manifest policy.requireSignatures block specifies which keys must sign before a release is valid. The build pipeline signature is necessary but not sufficient — a security review signature is required for production releases. An attacker who compromises the build pipeline can produce a valid pipeline signature but cannot produce the security review signature without also compromising that separate signing key.

This is defense against the exact attack pattern in CVE-2026-45321: the attacker hijacked one signing credential (the OIDC token from the runner). Requiring multiple independent signers means compromising one credential isn’t enough. Same discipline as digest-pinning and admission verifyImages.

Immutable release history on Arweave. When a release is published to Arweave, it’s permanent. It cannot be retracted, modified, or replaced. Shai-Hulud’s propagation mechanism relies on publishing poisoned new versions of packages. Existing versions on Arweave remain exactly as published. A deployment system that pins to specific Arweave transaction IDs is immune to version replacement attacks — there’s no mechanism to change what a transaction ID points to.

Vector 3: Credential Harvesting and Lateral Movement

The attack: Harvested credentials (npm tokens, GitHub PATs, AWS keys, Vault tokens, SSH keys) used to publish to additional packages, access internal systems, and establish persistence.

What helps:

Vault-first secrets architecture. A requireVaultBackedSecrets: true default means provider credentials live in HashiCorp Vault rather than in ~/.npmrc, environment variables, or CI/CD secret stores in cleartext. The Shai-Hulud payload targets over 80 credential locations including ~/.npmrc and common CI/CD secret patterns. Credentials that live exclusively in Vault behind short-lived dynamic secrets and OIDC authentication aren’t in the filesystem locations the harvester looks for.

Short-lived dynamic secrets reduce the window of utility for any harvested credential. A Vault token with a 1-hour TTL harvested by the worm is worthless after 60 minutes. A static npm token with no expiry is useful indefinitely.

ATRClaims scoping. Every MCP tool invocation that carries role, purpose, scope, and classification claims — validated at a Policy Enforcement Point before execution — means a harvested credential that attempts to invoke tools outside its declared scope fails at the PEP before the action executes. Lateral movement through a tool surface requires not just a valid credential but a valid credential with appropriate claims for the target action.

WORM audit trail with correlation_id. Every credential use is logged immutably with a correlation_id linking the action to the session, the agent, and the claim that authorized it. Shai-Hulud’s credential reuse for publication activity would appear in the WORM log as credential use from an unexpected agent_id or outside the expected session context. The log can’t be altered after the fact — Merkle-chained entries with Cosign-signed Git commits make retroactive modification detectable.

Vector 4: CI/CD IDE and Agent Persistence

The attack: Mini Shai-Hulud specifically targets AI coding agents and developer IDEs. Tenable’s remediation guidance calls out injected tasks in .vscode/tasks.json and Claude Code hooks in ~/.claude/settings.json.

This is the vector that’s new in Mini Shai-Hulud and specific to the AI tooling era. The worm isn’t just harvesting credentials — it’s establishing persistence in the AI agent configuration so that future agent sessions carry the malware’s hooks.

What helps:

Startup manifest verification (clawql doctor --smoke in ClawQL’s tooling) verifies the release manifest on startup. If the installed binary doesn’t match the expected Arweave-recorded hash, startup fails. A worm that modifies settings or injects hooks into the agent runtime is detectable at next launch.

Seatbelt profiles write-protect agent configuration. ~/.ClawQL/ is in the allowed-write list for ClawQL’s sandbox, but the Seatbelt profile can be extended to deny writes to specific subdirectories that contain security-critical configuration. A preinstall hook trying to inject into ~/.claude/settings.json fails at the kernel.

Falco detection rule for settings file modification:

- rule: AI Agent Config Modified
  desc: Detect modification of AI coding agent configuration files
  condition: >
    open_write and
    (fd.name contains "/.claude/settings.json" or
     fd.name contains "/.cursor/settings.json" or
     fd.name contains "/.vscode/tasks.json") and
    not proc.name in (allowed_config_writers)
  output: >
    AI agent configuration file modified
    (user=%user.name proc=%proc.name file=%fd.name)
  priority: CRITICAL

When Shai-Hulud attempts to inject into ~/.claude/settings.json, Falco fires. The event lands in Loki via Alloy. The alert fires in Grafana OnCall. The operator is paged.

Tetragon enforcement kills the process attempting the write before it completes on Kubernetes-managed workloads.

Vector 5: The gh-token-monitor Persistence Daemon

The attack: After infection, a daemon polls GitHub every 60 seconds. If the stolen token is revoked, it triggers recursive file deletion as a defensive countermeasure — destroying evidence and punishing the victim for rotating credentials.

This is the most sophisticated persistence mechanism in Mini Shai-Hulud and the one that most clearly indicates an attacker thinking about incident response.

What helps:

Falco daemon detection:

- rule: Suspicious Systemd Service Installation
  desc: Detect installation of unexpected systemd services
  condition: >
    open_write and
    fd.name startswith "/etc/systemd/system/" and
    not proc.name in (known_service_installers)
  output: >
    Unexpected systemd service installation
    (proc=%proc.name file=%fd.name)
  priority: CRITICAL

A preinstall hook creating a systemd unit file fires this rule immediately. The alert fires before the daemon is active.

Tetragon network policy blocks the gh-token-monitor daemon’s outbound connection to api.github.com from unexpected process contexts. If the connection isn’t made, the daemon can’t detect token revocation and can’t trigger the wiper.

The correct remediation order: Tenable’s guidance to find and remove the daemon before rotating credentials is critical. The sequence: (1) isolate the system from the network, (2) find and kill gh-token-monitor (search for the launchctl/systemd unit), (3) rotate credentials with the daemon unable to reach GitHub, (4) proceed with full incident response. Same scripted order instinct as forensic-ready IR: preserve and eradicate persistence before you revoke in a way the daemon can observe.

Vector 6: The Integration Catalog Attack Surface

This is the vector that isn’t in the Tenable FAQ but is worth naming explicitly for any platform that loads integrations from upstream.

Mini Shai-Hulud’s March 2026 precursor wave compromised the Aqua Security Trivy scanner — the tool most teams use for vulnerability scanning in CI/CD pipelines. This is elegant from an attacker’s perspective: compromise the security tooling, and security-conscious teams running CI/CD pipelines that include security scanning become distribution vectors.

Any platform that loads integrations from upstream sources is potentially exposed to this attack pattern. An integration catalog that says “add any MCP server from a URL” and then runs whatever it downloads is one compromised integration source away from distributing malware to every user.

Catalog design that responds to this:

Every integration in the catalog is mirrored to a controlled Harbor registry or R2 bucket before being made available. Users pull from the mirror, not from the upstream source. Silent upstream updates — including malicious ones — cannot reach users because users aren’t pulling from upstream.

Every mirrored integration is scanned with Trivy and OSV-Scanner against current CVE databases. It’s Cosign-signed. It has an SBOM. The SHA-256 of the artifact is recorded in the Layer 0 manifest.

Startup checks verify that installed integrations match their expected manifest hashes. A compromised upstream that pushes a malicious update to their registry doesn’t affect pinned consumers — the mirror is pinned to the hash that was in the manifest at approval time.

“Submit your API to the directory” is how open catalogs often work. Reviewing every integration before it ships is a trust signal, not a bottleneck. The Trivy scanner compromise is why.

The Defense Posture: What Stops What

Here’s the honest picture of which layers address which attack vectors in the Shai-Hulud campaign:

Attack VectorStopped ByLimits DamageDoesn’t Help
preinstall hook credential harvestSeatbelt read deny on ~/.aws, ~/.ssh, ~/.npmrcWORM log of failed read attemptsPrompt engineering, model safety
OIDC token extraction from runner memoryMultiple required signers on release manifestShort-lived Vault tokens reduce windowSLSA attestation alone
Poisoned package version replacementArweave content-addressed pinning, manifest SHA-256SBOM diff detects new dependenciesGitHub tag-based pinning
Credential use for lateral movementATRClaims scoping at PEP, Vault short-lived tokensWORM audit detects anomalous useStatic long-lived credentials
IDE/agent config injectionSeatbelt write-protect, Falco alert, startup manifest verifyStartup hash check catches modificationTrust-based agent config
gh-token-monitor wiper daemonFalco systemd rule, Tetragon network blockNetwork isolation before rotationCredential rotation before daemon removal
Compromised security tooling as vectorHarbor/R2 mirror, Trivy scan, Cosign sign, manifest pinStartup smoke verifyPulling from upstream directly
Open-sourced worm copycatsSame posture — defenses are content-based, not signature/IOC-basedPattern-based detection, not IOC-basedBlacklisting specific IOCs from original campaign

The last row matters. TeamPCP open-sourced the worm code and encouraged modifications. IOC-based detection — block this IP, flag this hash, blacklist this package name — is ineffective against copycat campaigns that change their infrastructure while keeping the same attack pattern. Content-based defenses (requiring specific content hashes, blocking writes to credential locations, requiring multiple signing keys) work against the pattern regardless of which threat actor is running it.

What Doesn’t Help (And Why)

Being honest about limitations prevents people from building false confidence in controls that won’t protect them.

SLSA attestation alone. The TanStack wave proved this. Attestation is a process control. When the process itself is compromised, attestation is evidence of the attack, not protection against it. Continue using SLSA — it raises the bar for most attackers — but layer content-addressed artifact pinning on top.

Signature verification without multiple signers. CVE-2026-45321 involved valid Sigstore signatures. The pipeline was legitimate. The signing was real. A single signing key on the legitimate build pipeline is insufficient when that pipeline can be compromised. Require independent signatures from multiple parties before promoting to production.

Dependency scanning on install. Running npm audit or Snyk at install time detects known-bad packages. It does not detect newly poisoned versions that haven’t yet been flagged. The window between publication and detection for Mini Shai-Hulud was measured in hours. For a team that installs daily, “scan on install” misses the window.

Pinning to tags rather than commit SHAs in GitHub Actions. Shai-Hulud’s cache poisoning attack works by compromising what the tag resolves to. A uses: actions/checkout@v4 that resolves to a different commit after cache poisoning runs malicious code. Pin to immutable commit SHAs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683.

Rotating credentials as the first response step. The gh-token-monitor daemon triggers recursive file deletion on token revocation detection. Find and remove the daemon, isolate the system from the network, then rotate credentials.

The LiteLLM Parallel

Mini Shai-Hulud is the most sophisticated documented supply chain attack against the AI tooling ecosystem. It isn’t the only one.

The LiteLLM supply chain compromise in March 2026 followed a different vector — a compromised package in LiteLLM’s dependency tree rather than LiteLLM’s own pipeline — but produced the same fundamental outcome: teams running an AI inference gateway that told them to trust it were running malicious code.

LiteLLM is Python. Its dependency tree includes hundreds of packages. Any one of them is a potential vector. The March 2026 incident showed that the vector exists and gets used.

A TypeScript inference path with Cosign-signed provider adapters, SBOM per build, Layer 0 manifest pinning, and startup hash verification narrows the surface and makes “was this the binary we published?” a machine-checkable question. That isn’t immunity — TypeScript packages can be compromised; content hashes can be forged if the signing infrastructure is compromised. The point isn’t “TypeScript is inherently safer than Python.” The point is that the defense posture — mirror, scan, sign, pin, verify — is applied consistently and is independent of which package manager the attacker is targeting.

If Shai-Hulud had targeted TypeScript packages exclusively, teams running LiteLLM would have been unaffected by that specific campaign. Teams with catalog mirrors and manifest pinning for integrations, plus content hash verification for the inference binary itself, would have been protected by the pattern, not by hoping the attacker chose npm over PyPI.

Practical Hardening Steps Right Now

If you’re running npm-based tooling, AI coding agents, or CI/CD pipelines after reading this:

Immediate:

# Check for gh-token-monitor persistence (do this before rotating tokens)
launchctl list | grep gh-token     # macOS
systemctl list-units | grep gh-token   # Linux

# If found, remove before credential rotation
launchctl remove gh-token-monitor   # macOS
systemctl disable --now gh-token-monitor  # Linux

# Then rotate credentials

CI/CD hardening:

Replace pull_request_target with pull_request for any workflow that executes code from PRs. If you need pull_request_target for access to secrets, explicitly scope the secrets to named steps and never pass them to code from the PR:

# Dangerous
on: pull_request_target
jobs:
  test:
    steps:
      - uses: actions/checkout@v4 # checks out PR code
      - run: npm test # PR code runs with repo secrets available

# Safer
on: pull_request_target
jobs:
  test:
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Run tests (no secrets)
        run: npm test
      # Separate job for secret-requiring steps, triggered only on trusted merge

Pin GitHub Actions to commit SHAs:

# Vulnerable to tag-moving attacks
- uses: actions/checkout@v4

# Pinned to immutable commit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

Add cache isolation between fork-originated and maintainer-originated workflows. The TanStack wave specifically exploited shared cache between trusted and untrusted contexts.

Dependency hygiene:

# Install without running lifecycle scripts by default
npm install --ignore-scripts

# Explicitly allowlist trusted scripts
npm install --ignore-scripts
npm rebuild trusted-package-with-binary  # only for known-needed postinstall scripts

If you run ClawQL locally:

# Verify installation matches the published manifest
clawql doctor --smoke

# Enable Seatbelt containment for agent harnesses
clawql sandbox init
clawql sandbox verify

# Check for injected hooks in Claude configuration
cat ~/.claude/settings.json | grep -i hook
cat ~/.claude/settings.json | grep -i exec

The Honest Summary

Mini Shai-Hulud proved three things that will shape supply chain security for years:

First: SLSA Build Level 3 provenance attestation is insufficient as a standalone control. When an attacker hijacks the legitimate pipeline, valid attestations are produced. Content-addressed artifact pinning catches what process attestation misses.

Second: Self-propagating supply chain worms are now a documented, multi-generation, open-sourced attack class with active copycat campaigns. The specific IOCs from the original Shai-Hulud campaign are useful for detection. They are not the boundary of the threat — the open-sourced code makes them table stakes.

Third: AI coding agents are now explicitly targeted for persistence. The ~/.claude/settings.json injection in Mini Shai-Hulud’s persistence toolkit means that agent configuration files are attack surfaces that require the same protection as credential stores.

The defense posture that addresses all three: mirror rather than pull directly from upstream, sign with multiple independent keys, pin to content hashes rather than tags, verify on every startup, contain agent processes at the kernel level so credential harvesting fails before it completes, and treat AI agent configuration files as security-critical infrastructure.

None of these controls are exotic. Several of them were available in 2021 when the colors/faker sabotage happened. The difference is that the threat model has matured to the point where “we trust packages we’ve used before” is no longer a sufficient posture.

The worm is open-source. The carnage is documented. The defenses exist.


The Layer 0 release Manifest, integration catalog Mirror, Cosign signing pipeline, and clawql sandbox module are documented at docs.clawql.com. For the full supply chain security architecture including the DevSecOps boilerplate: github.com/danielsmithdevelopment/DevSecOps-boilerplate.

About the author

Daniel Smith builds ClawQL, an agent operating system for token-efficient discovery and execution over APIs — with observability, hardened tool boundaries, and production routing for LLM workloads. He writes here about the systems problems behind shipping agents.