On October 7, 2026, at about 01:20 UTC, an attacker used a
compromised repository administrator account to push malicious code
directly to the main branch of tensorlakeai/tensorlake. The 13
commits, e90c47b through
6386121, added a preinstall
hook, a loader (lib/setup.mjs), and a payload
(lib/Math_Symbol.js) that
steals credentials and spreads itself.
On October 8, the attacker manually triggered the project’s release
workflow, which built, signed, and published [email protected] at 01:12:07
UTC. npm removed the version about three hours later, and the
maintainers reverted the code in pull request (PR) #1016 and
released a clean 0.5.145. StepSecurity, Socket, SafeDep, Aikido, and
Endor Labs published analyses of this compromise starting October 8, and
we link their work where it overlaps ours.
This activity belongs to the campaign known as Mini Shai-Hulud. The
payload tags itself with WORMTAG =
"tensrlake", a package-specific marker that, as Aikido
noted, suggests a newly seeded compromise rather than reinfection from
an earlier wave. According to Endor Labs, Tensorlake gets about 12,000 weekly
downloads and provides an SDK for
AI agent sandboxes, environments where
cloud, GitHub, npm, and AI tool credentials are often within reach. The
payload runs at install time, whether or not the SDK is ever used.
One behavior changes how responders need to act. Under a specific fallback path, the payload installs a token monitor that checks GitHub every 60 seconds for up to 24 hours. If GitHub returns an HTTP status from 400 to 409, including the 401 that follows token revocation, the monitor deletes the user’s home directory. Remove the monitor before you revoke any token.
TL;DR
-
Affected:
[email protected]on npm, plus six native platform packages published at the same version. We found no payload in the native packages, as Endor Labs also reports, but treat them as part of the affected release. We haven’t identified any malicious Python Package Index (PyPI) or Cargo release. - Exposure: npm recorded 15,664 tensorlake downloads across 100 versions from October 1 to 8. Because npm removed 0.5.144, its own count isn’t available, so these figures can’t show how many times the malicious version was installed.
-
Behavior: The payload goes after GitHub, npm, cloud,
Kubernetes, and Vault secrets, along with browser passwords and wallet
extension data. It can run JavaScript sent by its operator, plant a
workflow that dumps GitHub Actions secrets, republish npm packages with
Sigstore provenance attached, and add
.claudeand.vscodefiles to repositories. What it actually obtains depends on what’s present on the host. - Response: Isolate affected hosts and remove the token monitor before revoking any GitHub token. Then rotate everything the host could reach and rebuild it from a trusted image.
What we confirmed
We analyzed the [email protected] package
files, the tensorlakeai/tensorlake commit
history, and npm registry metadata.
The malicious code entered main through 13 commits from one named
maintainer, starting October 7. Nine of them show GitHub’s Verified
badge. Commit c79bc47,
titled “Add preinstall script to package.json,” adds "preinstall": "node
lib/setup.mjs", and the last clean commit is 52f19c8. The attacker also bumped
the version in pyproject.toml and Cargo.toml to 0.5.144 but changed
nothing else in them.

The published package adds two
files. lib/setup.mjs is the loader. It
exits if it detects common CI markers, reuses Bun if it’s
already installed, and otherwise downloads Bun 1.3.13 from Bun’s
official GitHub release into a temporary folder, runs the payload, and
deletes the folder. The loader contacts no attacker infrastructure
itself; its only network call is that Bun download. lib/Math_Symbol.js is the
obfuscated payload. It sets globalThis.WORMTAG = 'tensrlake'
and decrypts its embedded strings, scripts, and configuration at
runtime. The decoded payload carries two RSA public keys used by its
cryptographic routines, including commit signature verification.
Version 0.5.144 declares no new npm dependencies. Bun arrives at install time, outside the declared dependency graph, so tools that inspect only declared dependencies won’t see it. The previous release, 0.5.143, has no install-time lifecycle script.
How the malware executes
When [email protected]
installs with lifecycle scripts enabled, its preinstall hook runs lib/setup.mjs, which launches the
payload.

The payload picks one of two profiles, chaindrop or tanstack, from the WORM_PROFILE environment
variable, and defaults to chaindrop. Each profile uses
different payload filenames and execution paths, and in chaindrop the malware can stay
active in a continuing command loop. Everything described below is
something the code attempts. What it actually achieves depends on what
exists on the host, the permissions of the credentials it finds, and
whether the relevant services are reachable.

The collectors search local files, environment variables, process data, and cloud APIs. They target AWS credentials, AWS Secrets Manager, AWS Systems Manager Parameter Store, Kubernetes service account tokens and namespace secrets, and HashiCorp Vault tokens and key-value data, along with files belonging to a long list of developer tools and infrastructure services.



The malware validates any GitHub token it finds against the GitHub user API and reads the returned OAuth scopes to see whether the token has repository or workflow access. That result decides which GitHub operations it attempts next.

With workflow access, the malware goes beyond listing secret names.
It creates the branch dependabot/github_actions/format/setup-formatter
and commits .github/workflows/run-copilot.yml
as github-copilot[bot] with
the message Add Copilot
workflow. The workflow writes toJSON(secrets) to format-results.txt and uploads it
as an artifact named format-results. The malware then
polls for the run, downloads the artifact, and tries to delete the run
and the branch.



For command and control (Command and Control (C2)), the malware opens an HTTPS channel and evaluates any
nonempty JavaScript returned in the code field of the JSON
response. It can
also resolve a destination hostname through an Ethereum contract queried
over public JSON-RPC services. As a fallback, it creates a public
repository in the victim’s GitHub account with the description Shai-Hulud: Here We Go Again and
writes result envelopes containing host and user details alongside an
encrypted payload.

When token inclusion is enabled, the malware Base64 encodes the
GitHub token twice, adds it to the outgoing envelope, and installs a
monitor. The monitor checks the GitHub user endpoint every 60 seconds
for up to 24 hours. If the response status is anywhere from 400 to 409,
it runs a stored handler. On Windows, that handler recursively deletes
the directory in USERPROFILE.

On Linux and macOS, the malware pipes an embedded Bash script into a
process started with Bun.spawn, passing the token and
handler as arguments. The script installs the monitor in ~/.local/bin and stores the token
and handler in ~/.config/gh-token-monitor. On
macOS it registers a LaunchAgent, and on Linux it enables a systemd user
service and tries to enable lingering. The handler is rm -rf ~/, which deletes the
user’s home directory.

The string IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner
isn’t the trigger. The malware uses it as the prefix of dead-drop commit
messages that carry a stolen token, and a separate routine searches
GitHub for commits with that prefix, decodes the suffix, and keeps any
recovered token that has repository scope. The real trigger is the HTTP
status. Because revocation produces a 401, revoking a token while the
monitor runs can wipe the host.

The malware can also republish npm packages. It
downloads a package’s metadata and tarball, adds a preinstall script and
its payload files, increments the patch version, rebuilds the archive,
and attempts publication. A separate path adds @opensearch/setup as an optional
dependency to another package.

Before publishing, the malware tries to attach Sigstore provenance. It hashes the rebuilt tarball with SHA-512, builds a statement (in the in-toto format) that claims a GitHub Actions build produced the package, requests an OpenID Connect (OIDC) token from GitHub Actions, gets a signing certificate from Fulcio, signs the statement as a Dead Simple Signing Envelope (DSSE), and records it in Rekor, Sigstore’s transparency log. If any of these steps fails, it publishes anyway. Valid provenance on a package doesn’t prove its code is safe, a point StepSecurity and Endor Labs also make.

Finally, the malware can push VS Code and Claude configuration files
to eligible branches of repositories the token can write to: .vscode/tasks.json, .vscode/setup.mjs, .claude/settings.json, .claude/setup.mjs, and a copy of
the payload in .claude.
These files contain editor and assistant setup content that invokes
further setup scripts, which gives the malware another route to
execution in those repositories.

Impact and blast radius
The only malicious release is tensorlake 0.5.144. The six native platform packages
published in the same run contain no install script and no payload we’ve
found, but you should still avoid them. Versions 0.5.143 and earlier are
clean. Version 0.5.145 has no install-time scripts and is now a small
compatibility package that forwards to @tensorlakeai/tensorlake, where
the SDK has moved.

npm recorded 15,664 tensorlake downloads from October 1 to 8. The zeros for October 6 and October 8 likely reflect gaps in npm’s reporting rather than an absence of downloads. Most downloads in the period were for older versions, led by 0.5.97 with 10,820. npm reports no figure for 0.5.144 because npm removed the version, so its data can’t show how many times the malicious version was installed.

You’re potentially affected if any install resolved 0.5.144 between
01:12:07 UTC on October 8 and its removal at about 04:15 UTC. That
includes installs without a lockfile or with caret or tilde ranges,
automated dependency updates, and fresh container or image builds.
Anyone who pulled main between the malicious commits and the revert,
then ran npm install in typescript/, also ran the hook.
pnpm 10 and later blocks dependency lifecycle scripts by default, so
pnpm users were exposed only if they allowed scripts for this package.
Because the loader exits when common CI variables are set, hosted and
self-hosted GitHub Actions and GitLab runners skip the payload, which
leaves developer workstations most exposed. Build systems that don’t set
those variables, and container builds that don’t pass them through,
would still run it.
On an infected host, treat every secret the installing process could reach as exposed. That includes npm publish rights and GitHub access, which enable further supply chain compromise, as well as cloud credentials, Vault and Kubernetes secrets, SSH and TLS keys, AI tool tokens and Model Context Protocol (Model Context Protocol (MCP)) configuration, browser sessions and saved passwords, cryptocurrency wallets, and messaging data. A planted workflow can also expose the Actions secrets of any repository the stolen token can write to. If the worm used its GitHub dead drop, it publishes the victim’s token (Base64 encoded) in a public commit message, and the token monitor may be running.
Remediation
Remove [email protected] and the six
native packages at 0.5.144 from lockfiles, caches, images, and build
artifacts. Upgrade to 0.5.145 or pin to 0.5.143, verified against the
registry.
Order matters when rotating credentials because of the dead man’s switch:
- First, isolate the host and preserve evidence.
- Second, remove the token monitor persistence (paths in the indicators table below). Revoking the GitHub token while the monitor runs can trigger the wipe.
- Third, revoke the GitHub token and rotate every credential the host could reach, including npm, cloud, Vault, Kubernetes, SSH, Docker, CI, and AI tool secrets.
- Finally, invalidate browser sessions and move any cryptocurrency to new wallets created on a clean device.
Then rebuild the host from a trusted image. Uninstalling the package doesn’t remove persistence or undo credential theft.
Audit GitHub for unexpected commits to .claude, .vscode, or .github/workflows, for fake
Copilot workflows, and for new repositories described as Shai-Hulud: Here We Go Again.
Review npm publish history, Actions runs, and Vault audit logs for the
exposure window. Going forward, disable lifecycle scripts in CI by
default, prefer short-lived OIDC credentials, require human approval for
publishing, and install from committed lockfiles with npm ci, with critical SDKs pinned
to exact versions. We cover centralized package controls in more depth
in “Protecting yourself against malicious open-source packages.”
To check for exposure, run npm
ls tensorlake and search lockfiles for 0.5.144. Look for lib/setup.mjs and lib/Math_Symbol.js matching the
hashes below. Check for persistence in ~/.config/systemd/user, ~/.local/bin, ~/Library/LaunchAgents, or
Windows scheduled tasks. In endpoint detection and response (EDR)
telemetry, watch for node
lib/setup.mjs spawning Bun during npm install, traffic to iseekaigogo[.]com, and unexpected
Ethereum RPC connections from developer machines.
Incident timeline
All times UTC.
-
October 7, 01:20: The first malicious commit,
e90c47b, addslib/setup.mjsandlib/Math_Symbol.jsto main. -
October 7, 01:24: Commit
c79bc47adds the preinstall hook. -
October 7, 01:43 to 23:41: Another 11 commits update
the payload and set all package versions to 0.5.144, ending with
6386121. - October 8, before 01:12: The attacker manually runs the project’s release workflow.
-
October 8, 01:12:07:
[email protected]is published to npm. SafeDep, StepSecurity, and Socket flag it within 11 minutes, and StepSecurity reports it to the maintainers in GitHub issue #1014. - October 8, about 04:15: npm removes 0.5.144.
- October 8, 04:32: The maintainers merge PR #1016, which reverts the malicious commits, blocks install scripts in CI, and bumps all packages to 0.5.145. They also block direct pushes to main, require signed commits, and require a second reviewer for every npm publish.
- October 8, 20:24:05: tensorlake 0.5.145 is published as the clean release.
- October 9: 0.5.145 is the latest version, 0.5.144 is no longer available, and we’ve found no malicious PyPI or Cargo release.
Indicators of compromise
The table lists the indicators most useful for detection. The payload also targets 14 browser wallet extensions, including MetaMask, Phantom, Coinbase Wallet, and Rabby.
| Type | Indicator |
|---|---|
| npm package |
tensorlake 0.5.144,
plus six native platform packages at 0.5.144 |
| Install hook | "preinstall": "node
lib/setup.mjs" |
| SHA256, lib/setup.mjs | 25a0735d0db7dc40e5d45ce42d9c106067e6a66e184d967cfecfab17c3bcb5ef |
| SHA256, lib/Math_Symbol.js | b50a00900399ba99fb6ce1fc151519cb99d44320ef2a631f2237e1aea0ad6fec |
| C2 domain | iseekaigogo[.]com |
| Ethereum resolver contract | 0xb614155Fd88114d40549b259457Bcf921Df091B9 |
| Contract wallet | 0x779f83aE56309682beDb04816c19d358c4B21040 |
| Linux persistence |
~/.local/bin/gh-token-monitor.sh,
~/.config/gh-token-monitor/,
~/.config/systemd/user/gh-token-monitor.service
|
| macOS persistence | ~/Library/LaunchAgents/com.user.gh-token-monitor.plist |
| Windows persistence | Scheduled task gh-token-monitor running monitor.ps1, %LOCALAPPDATA%\gh-token-monitor
|
| Repository files |
.claude/settings.json,
.claude/setup.mjs, .claude/math_init.js, .vscode/tasks.json, .vscode/setup.mjs
|
| Malicious workflow |
.github/workflows/run-copilot.yml
on branch dependabot/github_actions/format/setup-formatter,
committed as github-copilot[bot] with the
message Add Copilot
workflow
|
| Strings |
WORMTAG = "tensrlake",
IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner,
Shai-Hulud: Here We Go
Again
|
| Dead-drop repositories | Public repositories named from two Dune terms and a number, such as
sardaukar, mentat, or fremen combined with sandworm, melange, or stillsuit
|
| Malicious commits |
e90c47b through 6386121 in tensorlakeai/tensorlake (last
clean commit: 52f19c8)
|
Checkmarx Security Research Team
NPM
Open-Source Security
Supply Chain Security


