The Short Version
What: A malicious [email protected] hit npm at 01:12 UTC on October 8, 2026. No typosquat this time. It’s the real Tensorlake TypeScript SDK, built by the project’s own workflow with valid Sigstore provenance. A preinstall hook uses Bun to run an 856 KB obfuscated ChainDrop / Shai-Hulud payload.
Impact: Socket counts about 12,000 weekly downloads, Aikido over 100,000 lifetime installs. Where the hook ran, it steals credentials, browser passwords, and AI tool configs. If the stolen GitHub token belongs to an account with no org memberships, it also plants a monitor that tries to wipe the home directory when that token is revoked.
Status: Socket flagged it within 11 minutes, and The Hacker News reports npm has pulled it. Tensorlake reverted the commits in pull request #1016. As of writing, 0.5.145 hadn’t shipped and six tensorlake-native-* packages were still live at 0.5.144. Installed it? Back up, remove the monitor, then rotate.
The Long Version
Are You Affected?
Two questions matter. Did 0.5.144 resolve anywhere you care about, and did its install hook run there?
The first comes down to how you install. tensorlake is still on a 0.x line, so a range like ^0.5.140 happily accepts any later 0.5 patch. Projects installing from an existing lockfile with npm ci stayed put. The risk came from resolving fresh while the bad version was up, through npm update, a Dependabot or Renovate PR merged that morning, a first-time install, or a Dockerfile that copies package.json without the lockfile. Anyone who pulled the repo’s main branch between October 7 and Tensorlake’s revert and ran npm install in typescript/ ran the hook too, no registry involved.
On the second question, nothing had to import the SDK. Installing it with lifecycle scripts enabled was enough, even in a build that failed a minute later. With ignore-scripts=true in .npmrc, the hook never fired. npm 12 and pnpm 10 and later hold dependency scripts until you approve them (npm 11 only warns), and Bun only runs them for packages on its trusted list, but check your config before relying on that. StepSecurity found the loader exits when it detects CI, which puts developer laptops first in line. That lowers CI risk without ruling it out, since Socket still found CI credential collection in the payload.
The rg line is rough triage, so treat any 0.5.144 on a tensorlake line as a lead. It skips gitignored and binary files, which means an older binary bun.lockb won’t show up:
npm ls tensorlake --all
npm ls --all | grep tensorlake-native
pnpm why tensorlake
yarn why tensorlake
rg -n 'tensorlake' -g '*lock*' -g 'npm-shrinkwrap.json' .
Then check the disk and the user profile:
find "$HOME" -path '*/node_modules/tensorlake/lib/Math_Symbol.js' 2>/dev/null
ls -la ~/.config/gh-token-monitor ~/.local/bin/gh-token-monitor.sh 2>/dev/null
Widen the find to build caches and runners, or use EDR on managed fleets. Confirm any Math_Symbol.js hit with the hashes in Mitigation. A gh-token-monitor directory means the trap is armed, so go straight to Mitigation. A missing one doesn’t clear the host, since the monitor only arms for some accounts.
What the Tensorlake Attack Is
Tensorlake runs untrusted, model-generated code in isolated sandboxes, and the npm SDK is how TypeScript apps drive them. Teams wall off whatever their agents write, then install the SDK on a laptop that can reach everything. The sandbox never failed. The malware ran on the installing machine before any sandbox was involved.
The attacker came in through the source repo. Per StepSecurity, the first malicious commit, e90c47b, landed on main at 01:20 UTC on October 7, followed by seven more that tuned the payload and added the preinstall line. None went through a pull request. Tensorlake’s own write-up says they came from a repo admin account through the GitHub web UI, which is why Aikido saw them marked verified. GitHub signs web UI commits itself, so the badge showed a logged-in account and nothing more. How that account was compromised hasn’t been published.
About a day later, the same account manually dispatched the repo’s publish_npm.yaml workflow. It built, signed, and published 0.5.144. Socket’s scanner flagged it at 01:23:10 UTC, and StepSecurity reported it to the maintainers in GitHub issue #1014. The filenames match August’s ChainDrop wave through keyv, and Aikido found a WORMTAG marker pointing to a fresh compromise rather than leftover reinfection.
How the Hostage Token Works
When the payload gets a GitHub token from an account with no organization memberships, it installs a service called gh-token-monitor, according to SafeDep’s decode. Tokens from accounts that belong to an org get stolen just the same, without the trap. That’s a systemd user unit on Linux, a LaunchAgent on macOS, and a logon scheduled task on Windows. Every 60 seconds, for up to 24 hours, it calls api.github.com/user with the stolen token. According to StepSecurity, once GitHub rejects the token, it runs rm -rf ~/ on Linux and macOS or a PowerShell delete of the user profile on Windows. The code even carries the string IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner, and GMO Flatt Security reports seeing files deleted on infected hosts.
Pull up almost any credential theft runbook and step one is revoking the token. On a host where this monitor is still running, that step can take the developer’s home directory with it, whether the revocation comes from the developer, an admin, or an automated playbook. If automation revokes tokens tied to flagged hosts, hold it for the affected hosts until the monitors are gone.
Pulling the network cable isn’t a guaranteed fix either, since nobody has published how the monitor handles a failed connection. Preserve critical data, make the containment call with your incident response team, and move fast, because the attacker can use that token the whole time.
Inside the Payload
The loader checks for CI, quits if it finds it, downloads Bun, and hands off to Math_Symbol.js, so the real work happens in a process that isn’t node.
It goes after npm tokens, including the OIDC exchange used by trusted publishing, and pulls from AWS instance metadata, Secrets Manager, and SSM Parameter Store, which GMO Flatt Security saw it read across every region. It probes a local Vault at 127.0.0.1:8200 and sweeps Kubernetes tokens, SSH keys, .env files, Docker config, and crypto wallets. Aikido found it dropping HackBrowserData for saved passwords and cookies. It also targets config and MCP files for Claude, Cursor, Kiro, Windsurf, and Zed, which often hold API keys in plain text.
Stolen data goes, encrypted, to iseekaigogo.com, the C2 domain hardcoded in the payload, or to a new public repo in the victim’s own GitHub account described as “Shai-Hulud: Here We Go Again”. As a fallback, Aikido found the malware reading an attacker-controlled Ethereum contract at 0xb614155Fd88114d40549b259457Bcf921Df091B9 for an alternate domain. Its last update, on September 21 from the wallet 0x779f83aE56309682beDb04816c19d358c4B21040, points at the same domain. A takedown helps, but one transaction can redirect it.
Then it spreads. With an npm token, it injects itself into the victim’s packages, bumps the version, generates Sigstore provenance, and republishes. With a GitHub token, it commits .claude/settings.json and .vscode/tasks.json to repos it can reach, using the author [email protected] and the message “chore: update dependencies”. Claude Code hooks can run shell commands and VS Code tasks can run on folder open, so those files can be a way back in, depending on how they’re set up and the next developer’s trust settings.
What Should Change at Intake
The provenance on 0.5.144 is real. It correctly says the package came from tensorlakeai/tensorlake via the project’s own workflow, which is all provenance ever claims. When an admin account commits straight to main, the real pipeline builds the malware and signs it. A policy that checks for provenance but never looks at the attested code would have accepted this release.
Lockfiles helped only through inertia, and a CVE scanner had nothing to match. Meanwhile the diff from 0.5.143 to 0.5.144 was hard to miss, with a new preinstall script, a new loader, and an 856 KB obfuscated file in a patch release. Few teams read tarball diffs for patch bumps, but in newer npm lockfiles a change to a package’s hasInstallScript entry is a cheap flag to watch.
Time helps too. Socket caught this in 11 minutes, so a 24-hour release-age gate (npm’s min-release-age, or pnpm’s minimumReleaseAge, which defaults to 24 hours from pnpm 11) would have kept it out of most builds. Tensorlake’s response, laid out in PR #1016, is also a decent template, with admin bypass removed from main, signed commits required, a second reviewer on npm publishes, and a CI tripwire that fails if a published manifest declares an install script. For consumers, hold any dependency update that adds or changes an install script. A package without one can still run code on import, but a new script is worth stopping for.
Mitigation
Order matters more than usual. Treat any host that ran the hook as compromised and work through your incident process.
Preserve critical data first. Copy uncommitted work and other irreplaceable data, not tooling, to external storage before touching tokens, services, or the network. Keep lockfiles and install logs as evidence.
Confirm the artifacts. Check workstations, build hosts, and images built on October 7 or 8 against the published SHA-256 values. lib/setup.mjs is 25a0735d0db7dc40e5d45ce42d9c106067e6a66e184d967cfecfab17c3bcb5ef and lib/Math_Symbol.js is b50a00900399ba99fb6ce1fc151519cb99d44320ef2a631f2237e1aea0ad6fec. GMO Flatt Security lists a file it calls artifact.tar.gz as 33108f494dad71369a9aa7cd6daa5402335d1a718b78320b9b687c0b2174fa67, and SafeDep’s copy of the publish log gives the npm tarball’s SHA-1 as 843a898ab72793568d77c53ff9282e6ce7c66aea. Hashes confirm a hit but won’t catch a modified copy.
Disarm the monitor before revoking anything. The file in ~/.config/gh-token-monitor/ holds the watched token. Treat it as sensitive evidence and keep it out of tickets and chat. StepSecurity published the Linux and macOS commands below, with a daemon-reload added here. Verify the names and paths on the actual machine before running them:
# Linux
systemctl --user disable --now gh-token-monitor.service
rm -rf ~/.config/gh-token-monitor
rm -f ~/.local/bin/gh-token-monitor.sh
rm -f ~/.config/systemd/user/gh-token-monitor.service
systemctl --user daemon-reload
# macOS
plist=~/Library/LaunchAgents/com.user.gh-token-monitor.plist
launchctl bootout gui/(id-u)"plist"
rm -rf ~/.config/gh-token-monitor
rm -f ~/.local/bin/gh-token-monitor.sh
rm -f "$plist"
On Windows, this read-only PowerShell lists scheduled tasks that run monitor.ps1, so you can remove them through your endpoint tooling:
Get-ScheduledTask | Where-Object {
"(_.Actions.Execute) (_.Actions.Arguments)" -match 'monitor\.ps1'
}
Confirm it’s gone before moving on. Deleting node_modules won’t touch it, and removing this monitor doesn’t prove there’s no other persistence.
Rotate from a clean machine. Revoke the watched GitHub token, then everything that account could reach, including npm, cloud, SSH, Kubernetes, Vault, and registry credentials, .env secrets, and AI tool and MCP API keys. Treat browser passwords and sessions as exposed, and kill active sessions while you’re at it.
Clean repos and check what you publish. Look for commits from [email protected] with “chore: update dependencies”, stray .claude/settings.json or .vscode/tasks.json files, unfamiliar workflows, and new public repos with the “Shai-Hulud: Here We Go Again” description. If a maintainer’s npm token was exposed, look for releases nobody on your team shipped.
Block and review egress. Block iseekaigogo.com by domain. Flatt lists 172.67.207.33 and 104.21.22.217 as its addresses, but those are shared Cloudflare IPs, so use them in log searches, not firewall rules.
Pin, enforce, and reimage when unsure. Hold tensorlake at 0.5.143, which also pins its native packages to 0.5.143, until Tensorlake publishes and confirms 0.5.145. Keep tensorlake-native-* off 0.5.144. Reinstall on a clean host, commit the lockfile, and use npm ci in CI. If you can’t account for every persistence point, rebuild the machine from a known-good image.
Detection
The process chain is the best signal. Look for npm or node spawning node lib/setup.mjs, which then downloads and launches bun. Bun appearing under a package install on a machine that never had it is suspicious by itself, and so are install children that read .npmrc, .aws, .kube, SSH keys, or browser profiles.
For persistence, alert on systemd user units, LaunchAgents, or ONLOGON tasks created under a Node or Bun process. A non-browser process polling api.github.com/user once a minute is about as specific a fingerprint as you’ll get.
How SecureChain for JavaScript Changes the Outcome
SecureChain for JavaScript moves the trust decision upstream, before a package reaches a laptop. It rebuilds packages from source in hardened, isolated environments and screens them for known malware, suspicious code patterns, and supply chain risk, blocking anything that fails inspection. A rebuild on its own wouldn’t have saved anyone here, because the poisoned code was sitting in the upstream source. Screening is what catches this, and a patch release that suddenly adds a preinstall hook, downloads a second runtime, and runs 856 KB of obfuscated JavaScript is exactly the kind of change it’s built to stop.
Discover SecureChain for JavaScript →
It’s a drop-in repository replacement compatible with JFrog Artifactory, Sonatype Nexus, and GitHub Packages, so the npm commands stay the same, and it ships SLSA Level 3 provenance, SBOM, and VEX output. It covers JavaScript, Python, Java, Go, Rust, and PHP. And when incidents like this push teams to freeze a dependency until it’s past end of life, TuxCare’s Endless Lifecycle Support can keep delivering security fixes for that version for as long as you depend on it. No forced upgrades, and no rewrites.
Why This Is Bigger Than One Package
May had a Mini Shai-Hulud wave through TanStack that carried valid SLSA provenance, and June had Mastra’s typosquat. August had ChainDrop through keyv and the arrayref compromise on crates.io. Now the same worm family has reached AI agent infrastructure. What keeps repeating is defenders treating an authenticated publisher or a valid attestation as proof the code is fine. Here, the package name and the provenance were both real. They just answered a different question than the one that mattered.
Discover SecureChain for JavaScript →
FAQ
Which Tensorlake versions are safe?
0.5.143 is the last clean release and was still npm’s latest at the time of writing. Move to 0.5.145 once Tensorlake publishes it, and keep tensorlake-native-* off 0.5.144.
Is it safe to revoke the stolen GitHub token right away?
Not if the monitor might still be running. On a host where gh-token-monitor is active, revoking its token can trigger deletion of the home directory or user profile. Preserve data, remove the monitor, then revoke and rotate, and do it quickly.