· 5 min read ·

Mini Shai-Hulud and the New Shape of npm Supply Chain Attacks

Source: openai

On May 23, 2026, OpenAI published a postmortem describing how a compromised TanStack package leaked an Apple Developer ID signing certificate from one of their build machines. The attack has been dubbed “Mini Shai-Hulud” because it borrows the propagation playbook of the original Shai-Hulud worm that tore through npm in September 2025, but with a smaller blast radius and tighter targeting.

If you ship anything through npm, this incident is worth sitting with for a few minutes. Not because OpenAI’s response was novel, but because the attack class is now mature enough to hit a security-conscious org with a CI pipeline that probably looked a lot like yours.

What actually happened

The TanStack family of packages (@tanstack/react-query, @tanstack/router, @tanstack/store, and friends) collectively pulls roughly 15 million weekly downloads from npm. On the compromise day, several of these packages shipped malicious postinstall scripts under patch-level version bumps. The payload was a derivative of the original Shai-Hulud bundle: a Node script that scraped environment variables, GitHub tokens, npm tokens, AWS credentials, and any nearby .npmrc or ~/.aws/credentials files, then exfiltrated them to a public GitHub repository created with the stolen token.

The “Mini” qualifier comes from what the worm did not do. The September 2025 variant attempted to self-propagate by republishing every other package the victim had publish rights to, which is how a single TruffleHog plugin compromise rapidly metastasized into 180+ infected packages within hours. The TanStack variant skipped that propagation step. It exfiltrated, then went quiet. Researchers at Socket and Aikido initially flagged it within about 90 minutes of publish, which is fast by historical standards but still long enough for thousands of CI runs to ingest the bad versions.

Why OpenAI’s signing cert mattered

OpenAI’s macOS apps (the desktop ChatGPT app, Codex, and the Whisper companion utilities) are signed with an Apple Developer ID certificate. That certificate lives on a build machine. The build machine, like most build machines, runs npm install against a lockfile. The lockfile pinned a version range that resolved to a compromised TanStack patch. The malicious postinstall ran with the privileges of the CI user, which had read access to the keychain entry holding the signing identity.

Apple’s Developer ID program is the trust anchor for unsigned-but-notarized macOS distribution. If a private key leaks, every binary signed with it becomes suspect, because an attacker can sign their own malware with the same identity and have it pass Gatekeeper checks on machines that have already approved that developer. Apple’s response to a confirmed leak is to revoke the certificate, which invalidates every binary signed with it, including the legitimate ones already in the wild.

That is why OpenAI’s notice gives macOS users until June 12, 2026 to update. After that date, Apple will revoke the old Developer ID, and any unupdated installation will start failing notarization checks on launch. The new builds are signed with a freshly issued certificate stored in an HSM, which the postmortem says is now isolated from the CI environment that runs untrusted package installs.

The signing-key-on-CI antipattern

The interesting design lesson here is not “npm bad.” It is that signing keys and dependency installs share a security boundary on most build systems by default, and that boundary was always thin.

The established mitigation is to split the build into two stages. Stage one runs in a permissive sandbox: pull dependencies, compile, run tests, produce an unsigned artifact. Stage two runs in a locked-down environment with no network access to npm, no ability to execute arbitrary postinstall scripts, and read access to the signing key. The artifact moves between stages as a hashed blob.

GitHub’s reusable workflows with environment protection rules make this pattern straightforward, and Sigstore’s cosign keyless signing sidesteps the problem entirely for container images by using short-lived OIDC-issued certificates instead of long-lived secrets. macOS code signing is harder because Apple does not yet offer a keyless equivalent, but you can put the key in an HSM or use notarytool with a session-scoped App Store Connect API key to limit exposure.

The OpenAI postmortem describes moving toward exactly this split. The signing stage now runs on a machine that has never seen node_modules. That is the right architecture, and it is also the architecture that nobody implements until after they have been bitten.

Why npm postinstall is still a thing

The deeper question is why arbitrary code execution at install time remains the default in 2026. The npm RFC tracker has had an open proposal to disable postinstall scripts by default since 2021. Yarn 2+ ships with enableScripts: false as a configurable default and pnpm offers onlyBuiltDependencies to allowlist exactly which packages get to run scripts. Both options exist. Both are off by default in most repos because turning them on breaks node-gyp, esbuild, sharp, and a handful of other packages that legitimately need to compile native code at install time.

This is the same dynamic that kept Python’s setup.py arbitrary-code execution alive for a decade after the security community started begging for declarative metadata. PEP 517/518 finally pushed the ecosystem toward pyproject.toml, but only because the tooling caught up enough that the migration cost dropped below the security benefit. npm has not had its PEP 517 moment. Until it does, the install graph is an arbitrary code execution graph.

Deno’s approach is worth comparing here. Deno does not run lifecycle scripts at all when consuming npm packages through npm: specifiers. If a package needs native compilation, you wire it up explicitly. The cost is friction; the benefit is that no transitive dependency you have never heard of can read your environment variables when you type deno install.

The propagation pattern is the real story

What makes Shai-Hulud and its derivatives different from the Event-Stream incident in 2018 or the colors/faker self-sabotage in 2022 is that the worm logic is now a reusable component. The original bundle is on GitHub. Forks have appeared with different exfiltration endpoints, different package targeting heuristics, and different evasion techniques. StepSecurity’s writeup on the original incident notes that the worm specifically searches for npm tokens with publish scope, then enumerates the maintainer’s other packages and republishes them with the same payload.

The Mini variant disabled propagation, probably because the operators wanted a quieter window to harvest the OpenAI signing cert before researchers caught up. That is a deliberate choice, not a limitation, and it tells you the attackers are now thinking about operational security at the same level the defenders are.

What to do this week

If you have not already, audit your CI environments for three things. First, whether your signing keys, deploy tokens, or cloud credentials are accessible during dependency installation. Second, whether you can disable lifecycle scripts in your package manager and ship an allowlist for the handful that legitimately need them. Third, whether you have a lockfile policy that pins exact versions and a renovation process that reviews diffs before merging dependency bumps.

None of this is glamorous. It is the same hygiene the security community has been advocating since 2018, and the cost of skipping it keeps going up. The TanStack maintainers did nothing wrong here; their account was compromised through a phishing route that targeted the npm 2FA reset flow, the same vector that hit ua-parser-js and coa years ago. The lesson is that you cannot assume your upstream is uncompromised, only that the blast radius when they are will be proportional to how much trust you handed them by default.

Was this interesting?