A Compromised Admin Account Put the Shai-Hulud Worm Into AI Sandbox Maker Tensorlake’s npm SDK
A poisoned Tensorlake SDK release reached npm through the project’s own release workflow with valid provenance, carrying a worm whose tripwire can wipe a home directory when a stolen GitHub token is...
Tensorlake provides isolated sandboxes for running untrusted, LLM-generated code. At 01:12:07 UTC on October 8, 2026, a poisoned build of its npm SDK, [email protected], went live carrying the credential-stealing Shai-Hulud worm. Socket flagged the release 11 minutes later, SafeDep flagged it at 01:20 UTC, and npm has since removed it. The Register reports that the package draws roughly 12,000 downloads a week, but none of the analyses behind this story says how many machines installed the bad version.
Table Of Content
How it got there matters more than the package. Tensorlake’s own revert pull request says the payload was “committed directly to main by a repo-admin account through the GitHub web UI” and then released by manually dispatching the project’s publish workflow, which “built and signed it with Sigstore provenance.” SafeDep adds that the attacker “did not need an npm token.” The result passes any check that asks only who built the package. It is the same shape as the keyv compromise we covered in August, when Microsoft named the worm ChainDrop, now aimed at tooling that AI agent developers install.
What happened, hour by hour
All times are UTC. Commit times come from GitHub’s API for the 13 commits between Tensorlake’s last clean commit and the final poisoned one, and publish times come from the npm registry’s own time log.
| When | What happened |
|---|---|
| Oct 6, 21:59 | npm 0.5.143 is published. Its provenance record names commit 52f19c8a, which Tensorlake calls the last clean commit. |
| Oct 7, 01:20 | The first poisoned commit, titled “Add files via upload”, adds the loader and payload files. A preinstall hook follows at 01:24, and SafeDep says the 01:43 commit repaired the JSON that edit broke. Four more uploads follow, the last at 03:44. |
| Oct 7, 03:57 to 23:41 | Six more commits, most of them bumps to version 0.5.144 and fixes to the platform packages’ version numbers. |
| Oct 8, 00:08 | The release workflow, publish_npm.yaml, is started by hand, according to SafeDep. The provenance record shows a workflow_dispatch trigger. |
| Oct 8, 00:17 to 00:19 | Six platform packages, tensorlake-native-*@0.5.144, are published. |
| Oct 8, 01:12:07 | [email protected] is published with the latest tag. |
| Oct 8, 01:20 and 01:23 | SafeDep and Socket flag it. |
| Oct 8, by 04:29 | npm has removed the version, according to Tensorlake’s pull request, opened at 04:29. The revert merges at 04:32. |
| Oct 8, 05:22 to 05:25 | The six platform packages get a 0.5.145 release. The SDK’s own 0.5.145 had not appeared at 19:26 UTC. |
A clean-looking release with valid provenance
I decoded the npm provenance record for one of the six platform packages published alongside the SDK, [email protected]. It is a SLSA provenance statement that names the publish_npm.yaml workflow on main, a workflow_dispatch trigger, a GitHub-hosted runner and source commit 6386121c, the last of the 13 commits. The record for the clean 0.5.143 has the same shape and points at 52f19c8a. Apart from the commit hash and the run number, the two are alike. SafeDep puts it plainly: “The provenance is valid for a build of poisoned source.” npm’s documentation says the same in general terms: provenance “does not guarantee the package has no malicious code.”
The Verified badges on the poisoned commits settle nothing either. Nine of the 13 show as Verified because, in the words of GitHub’s documentation, GitHub “will automatically use GPG to sign commits you make using the web interface.” The four unsigned commits are all version fixes. The badge shows who signed a commit, not what the commit does, and in the keyv case the commits were unsigned.
I also hashed the two payload files from the repository at that final commit, without running them. lib/setup.mjs is 32,645 bytes and lib/Math_Symbol.js is 856,501 bytes, and both SHA-256 values match the ones Socket published for the npm package, so the commits and the package carry the same code.
Tensorlake’s pull request lists its response: admin bypass removed and direct pushes to main blocked, a second person required to approve every npm publish, release jobs that install with --ignore-scripts, and a CI check that fails if any published manifest declares an install script. It also lists work still to do by a human, including locking the compromised account and rotating the secrets available to the release workflow.
What the payload does
I did not run or decode the payload, so the details below are Socket’s and SafeDep’s.
- Entry. The
preinstallhook runsnode lib/setup.mjs, an obfuscated loader that, per SafeDep, downloads Bun 1.3.13 from the official release and runslib/Math_Symbol.js. Socket notes that “Developers do not need to import the SDK or start an agent for the hook to run.” - Theft. npm and GitHub tokens, AWS credentials (instance metadata, ECS, Secrets Manager and Parameter Store), a local HashiCorp Vault, Kubernetes tokens and kubeconfig files, SSH keys,
.envfiles, crypto wallets, browser passwords, and configuration for AI development tools. Socket names Claude, Cursor, Kiro, Windsurf and Zed files, and SafeDep names~/.claude.jsonand~/.kiro/settings/mcp.json. - Command and control. A hard-coded domain,
iseekaigogo[.]com, with fallbacks through an Ethereum contract, signed GitHub commits and dead-drop repositories. SafeDep says any response can carry code that the worm runs witheval, and that it checks in every 45 to 90 seconds. - Spread. With an npm token, it adds the payload and a preinstall hook to every package the token can publish, bumps the patch version and publishes. With a GitHub token, SafeDep says, it commits Claude Code and VS Code hooks as author
claudeand plants a “Run Copilot” workflow that dumps repository secrets. - The tripwire. A watcher called
gh-token-monitorpersists as a systemd user service, a macOS LaunchAgent or a Windows scheduled task. SafeDep says it checks the stolen GitHub token every 60 seconds for 24 hours and deletes the home directory if GitHub returns a 40x response, for example after revocation. SafeDep adds that the watcher arms only for stolen tokens whose account has no organizations. Socket’s advice makes no such distinction.
Who was actually exposed
Four details narrow the field, and a fifth keeps the real number unknown.
- npm’s default install behavior. The hook only runs where dependency install scripts run. GitHub’s changelog for npm v12 says “npm install will no longer execute preinstall, install, or postinstall scripts from dependencies unless they are explicitly allowed in your project,” and the npm 12 configuration reference lists
allow-scriptswith an empty default. The registry shows npm 12.0.0 shipped on July 8, 2026, and 12.2.0 is now thelatesttag, so a default install on current npm should not have run this hook. npm 11 and earlier, and projects that allowed scripts, would have run it. I did not test other package managers. Socket words it the same way: “Where dependency lifecycle scripts are permitted, this hook gives the malicious loader an execution path during installation.” - CI is skipped. SafeDep’s decoded loader exits when
CIistrueor1, whenGITHUB_ACTIONSorGITLAB_CIistrue, or whenRUNNER_ENVIRONMENTisgithub-hosted, and SafeDep infers that the payload probably did not run on Tensorlake’s own release runner. Socket lists build runners among the hosts at risk, so a runner that sets none of those variables gets no such protection. - The Python SDK looks untouched. The only files the poisoned commits added are the two under the repository’s
typescript/libfolder, and the newesttensorlakerelease on PyPI was still 0.5.143 at 19:29 UTC, so the Python package does not appear to have shipped the poisoned version. The same commits did raise the Python and Rust version strings to 0.5.144. - A short window. The version was live from 01:12:07 UTC, and Tensorlake’s pull request says npm had removed it by 04:29, so at most about three hours and 17 minutes.
- Unknown scale. Socket and The Register cite about 12,000 downloads a week for the package, SafeDep cites about 106,000 a month, and npm’s download API reported 18,826 for September 28 to October 4. Those figures count every version, not the poisoned one, and Socket itself says its number “does not measure downloads of the malicious version or confirmed infections.”
What to do now
- Search lockfiles, build logs and deployed artifacts for
[email protected]. The version is gone from the registry, so also check internal mirrors, proxies and caches that may have kept a copy. - Treat any host where the hook ran outside CI as compromised, and mind the order. Socket’s instruction is “Remove the token monitor before revoking any tokens.” Then revoke and replace exposed credentials and rebuild the machine from a trusted source.
- Audit what those credentials could reach: unexpected npm publishes, commits by an author named
claude, new hook files, a “Run Copilot” workflow, and public repositories described “Shai-Hulud: Here We Go Again”. - If you must use the SDK, stay on 0.5.143 until a clean 0.5.145 appears. Its provenance record points at the commit Tensorlake calls clean, and the registry’s
latesttag for the SDK was still 0.5.143 at 19:26 UTC. - If you maintain packages, Tensorlake’s own list is a reasonable template: no admin bypass on the default branch, a second approver on the publish environment, and
--ignore-scriptsin release jobs.
Indicators to search for
- Package:
[email protected] - SHA-256 of
lib/setup.mjs:25a0735d0db7dc40e5d45ce42d9c106067e6a66e184d967cfecfab17c3bcb5ef - SHA-256 of
lib/Math_Symbol.js:b50a00900399ba99fb6ce1fc151519cb99d44320ef2a631f2237e1aea0ad6fec - Network (SafeDep):
iseekaigogo[.]com, with paths/routerand/hbd/ - Persistence (SafeDep):
gh-token-monitor.service,com.user.gh-token-monitor, a scheduled task namedgh-token-monitor, and the folder~/.config/gh-token-monitor/ - Payload first line (SafeDep):
globalThis.WORMTAG = 'tensrlake';
What is still unknown
- How the account was compromised. Tensorlake calls it compromised, but neither its pull request nor Socket nor SafeDep says how.
- How many machines ran the hook. No published count exists for the bad version.
- Whether the six platform packages at 0.5.144 are harmful. Tensorlake’s pull request says they still need to be unpublished, and the registry still listed all six at 19:26 UTC, next to new 0.5.145 releases. I did not download them.
- The SDK’s clean release. The Register reports that Tensorlake updated the version to 0.5.145. The registry showed 0.5.145 for the platform packages but not for the SDK itself at 19:26 UTC.
- Who is behind it. No source I read attributes this build to a named group.
- A fuller account. GitHub’s advisory database had no entry for the package when I queried it, and the pull request is the only first-party statement I found.
Tensorlake’s product exists to keep untrusted, model-written code away from the host. The install step of its own SDK ran outside that boundary, which is Socket’s point: “A compromised SDK creates exposure on the machine installing it, before generated code reaches a sandbox.” For detection, see our tutorials on malicious npm preinstall scripts and on npm packages that hide malware in runtime code, the route some other campaigns take to avoid install-time scanners.








No Comment! Be the first one.