Safeguard
Supply Chain Security•

tensorlake npm Compromise: Why Valid Provenance Did Not Stop It

tensorlake@0.5.144 ran a credential stealer from a preinstall hook despite valid npm provenance. Check if you installed it, what to rotate, what helps.

Safeguard Security Team
18 min read

On 8 October 2026, tensorlake@0.5.144 on npm shipped a credential-stealing worm that ran from a preinstall hook the moment it was installed. The project's own CI built it, and it carried valid provenance. If any laptop or CI job installed it, remove the persistence service first, then rotate every reachable credential from a clean machine and rebuild.

The bad version was live for under two hours, according to the maintainers' advisory. It still passed every check many teams treat as proof of a safe package: a real repository, verified commits, the official release workflow and a signed provenance attestation. Below: what happened, how to check, what to rotate, and what helps.

What happened

Tensorlake makes sandboxes for running untrusted, LLM-generated code, and tensorlake is its TypeScript SDK on npm. Socket and Endor Labs put its usage at roughly 12,000 weekly downloads; Aikido cites over 100,000 lifetime installs. Neither figure measures installs of the bad version.

According to Tensorlake's own advisory (GHSA-8g63-53c9-65j2), a compromised maintainer account was used to push malicious code directly to the repository, and the project's normal release workflow then published it. The advisory is explicit that 0.5.144 was not an authorised Tensorlake release.

Date and time (UTC)EventSource
4 Aug 2026Microsoft documents ChainDrop, a Mini Shai-Hulud variant spread across more than 400 npm packagesMicrosoft Security Blog
7 Oct, 01:20First malicious commit lands on the main branch under a maintainer's nameEndor Labs; StepSecurity via The Hacker News
8 Oct, 01:12:07tensorlake@0.5.144 published by the project's GitHub Actions release workflow, with valid provenanceEndor Labs, Socket, Tensorlake advisory
8 Oct, 01:23Socket flags the release, about 11 minutes after publishSocket
8 Oct, 02:54End of the window in which the version was available, about 1 hour 40 minutes in totalTensorlake advisory
8 OctGitHub publishes malware advisory GHSA-rqxj-g25x-4v9v (CWE-506)GitHub Advisory Database
8 OctTensorlake publishes GHSA-8g63-53c9-65j2 (CVSS 3.1 base 9.6) and merges PR #1016 reverting the bad commitsTensorlake on GitHub
9 OctThe public report, issue #1014, is closed after the advisory is publishedTensorlake on GitHub

Reports differ on a few details. Aikido says the repository was compromised for about 20 hours before the publish; Endor Labs' commit and publish times put the gap closer to a full day. The maintainers say they saw no usage of 0.5.144 based on user-agent monitoring, which is encouraging but is not the same as proof that nobody installed it.

Socket says the file names and loader structure match the ChainDrop wave Microsoft described in August. Aikido calls it a likely new compromise rather than a reinfection. For background on the worm family, see our explainer on the Shai-Hulud npm attack.

Who is affected

Package or setupStatusSource
tensorlake@0.5.144 on npmMalicious, removed from the registryTensorlake advisory, GitHub malware advisory
Six tensorlake-native-*@0.5.144 packages (darwin-arm64, linux-arm64-gnu, linux-arm64-musl, linux-x64-gnu, linux-x64-musl, win32-x64)Published in the same run and listed as affected; Endor Labs found no payload in them, but treat them as part of the bad releaseTensorlake advisory, Endor Labs
tensorlake@0.5.143 and earlierNot affectedTensorlake advisory, Endor Labs
0.5.145The fix PR bumps all packages to 0.5.145 so it supersedes the withdrawn version; the advisory says use it once publishedPR #1016, Tensorlake advisory
tensorlake on PyPI and crates.io, and the Tensorlake CLINot affectedTensorlake advisory
Any project that resolved tensorlake during the window with install scripts enabledTreat the machine or runner as fully compromisedGitHub malware advisory

Most at risk: anyone who added or updated the SDK in that window, or installed with a caret range such as ^0.5.140 and no lockfile. A lockfile pinned to 0.5.143 and installed with npm ci kept 0.5.143.

How it works

The install-time hook

The malicious manifest added a preinstall script that runs node lib/setup.mjs. npm runs lifecycle scripts automatically during install, so the code executed before any application code, and your project never had to import the SDK. Socket and Aikido describe setup.mjs as a small obfuscated loader that fetches the Bun runtime and uses it to run a large obfuscated payload, lib/Math_Symbol.js. Endor Labs notes the loader checks for CI environment variables; runners often hold the most powerful secrets.

What the payload does

Across the advisories, the payload:

  • Harvests credentials: npm and GitHub tokens, AWS and GCP credentials, Kubernetes and Vault tokens, SSH and GPG keys, .env files, browser passwords and crypto wallets, plus configuration for AI coding tools and MCP servers (Tensorlake advisory, Socket).
  • Probes cloud instance metadata endpoints for short-lived cloud credentials (Endor Labs).
  • Uses stolen npm and GitHub tokens to republish itself into other packages and repositories the victim can write to (Tensorlake advisory, Microsoft).
  • Writes .claude/settings.json and .vscode/tasks.json into reachable repositories so it runs again when someone opens the project in Claude Code or VS Code, according to StepSecurity as reported by The Hacker News.
  • Installs a persistent service called gh-token-monitor. The Tensorlake advisory says it "deletes the user's home directory if the stolen GitHub token is revoked."

Sources disagree on how the payload finds its command server. Socket says there is no hardcoded domain and the endpoint is resolved through an Ethereum contract via about 30 public RPC endpoints. Aikido says there is a hardcoded domain, with the Ethereum lookup as a fallback through three RPC endpoints. Endor Labs describes the domain as disposable and rotated on chain. For response purposes, the difference does not change what you do.

Why provenance did not help

npm provenance is a signed statement that links a package tarball to the source repository, commit and build workflow that produced it. For 0.5.144, every part of that statement was true. The commit was in the real repository. The workflow was the real release workflow. The attestation was valid.

What provenance cannot tell you is whether the commit should have been there. npm's own documentation says provenance "does not guarantee the package has no malicious code." It gives you a verifiable trail to audit, not a verdict. Endor Labs made the same point about this incident: provenance shows where a build came from, not whether the source was clean.

The same applies to verified commits. A signature proves the commit came from an account holding a particular key. If the account is compromised, the signature is still valid. In the earlier ChainDrop wave, Microsoft found malicious versions often had no matching source commit or tag. Here there was one, which made the release look more legitimate.

Treat provenance as necessary but not sufficient. It catches a package built somewhere it should not have been. It does nothing against a trusted pipeline fed untrusted source.

What to do now

Work through this list in order. Steps 3 and 4 are order-sensitive.

  1. Check every project for the version. In each repository, run:

    npm ls tensorlake --all
    

    Any result showing 0.5.144 means that project resolved the bad release.

  2. Check lockfiles, caches and CI logs. The installed tree only shows the present. Lockfiles in git history and CI logs show the past.

    # Lockfiles in the working tree
    grep -n -A3 'node_modules/tensorlake' package-lock.json | grep '"version"'
    grep -n -A2 '^"\?tensorlake' yarn.lock 2>/dev/null | grep '0.5.144'
    grep -n 'tensorlake.*0.5.144' pnpm-lock.yaml 2>/dev/null
    
    # Any lockfile change in git history that mentioned the bad version
    git log -p --since=2026-10-07 -- package-lock.json yarn.lock pnpm-lock.yaml | grep -n 'tensorlake.*0.5.144\|tensorlake-0.5.144'
    
    # The local npm cache
    grep -rl 'tensorlake-0.5.144' ~/.npm/_cacache/index-v5 2>/dev/null
    

    In CI, search build logs for tensorlake@0.5.144 or tensorlake-0.5.144.tgz, focusing on jobs that ran between 01:12 and 02:54 UTC on 8 October, as the Tensorlake advisory recommends. Check shared CI caches and any artifact proxy or mirror that may have kept a copy.

  3. Isolate and remove persistence before revoking tokens. Disconnect any affected machine or runner. Then look for and remove the gh-token-monitor service, following the platform steps in the Tensorlake advisory. Quick checks:

    # Linux
    systemctl --user list-units --all | grep -i gh-token
    ls ~/.config/gh-token-monitor ~/.local/bin/gh-token-monitor.sh 2>/dev/null
    
    # macOS
    launchctl list | grep -i gh-token
    ls ~/Library/LaunchAgents | grep -i gh-token
    
    # Windows (Command Prompt)
    schtasks /query /fo LIST | findstr /i gh-token
    

    Socket warns that revoking the GitHub token while the monitor is running can trigger the wipe. The Hacker News did not report a confirmed wipe in this incident, but the order still matters.

  4. Rotate credentials from a clean machine. Rotate everything the affected host could reach: npm tokens, GitHub tokens and SSH keys, cloud keys, Kubernetes and Vault tokens, GPG keys, values in .env files, and any secrets your CI injected into the job. The GitHub malware advisory says to rotate "from a different computer."

  5. Audit for spread. Check npm packages and repositories those tokens could write to. The Tensorlake advisory lists unexpected releases, commits authored by "claude," and a branch named dependabot/github_actions/format/setup-formatter as signs. Also review recent changes to .claude/settings.json, .vscode/tasks.json and .github/workflows/ in any repository the token could reach.

    git ls-remote origin | grep -i setup-formatter
    git log --since=2026-10-07 --all -- .claude/settings.json .vscode/tasks.json .github/workflows
    
  6. Pin a clean version and reinstall cleanly. Set tensorlake to exactly 0.5.143 (or 0.5.145 once you have confirmed it is published), remove cached copies, regenerate the lockfile, and reinstall with scripts disabled to start:

    npm cache clean --force
    npm install tensorlake@0.5.143 --save-exact --ignore-scripts
    npm ci --ignore-scripts
    
  7. Rebuild. Reimage affected laptops and rebuild runners and base images from a trusted source, as the Tensorlake advisory and Microsoft both recommend. Removing the dependency alone is not enough; Socket notes that "Removing the npm dependency alone does not remove a persistent implant."

For a fuller response runbook, see our incident response playbook for a compromised dependency.

What would have helped

None of these is a guarantee, and some would not have stopped this exact chain. Together they shrink the window, limit what one install can steal, and make "are we affected?" a quick lookup.

For teams that consume npm packages

  • Commit lockfiles and install with npm ci. npm ci installs exactly what the lockfile records and fails if package.json and the lockfile disagree. A pinned 0.5.143 stays 0.5.143. This is the single cheapest control against a short-lived bad release.
  • Add a release cooldown. Recent npm versions document min-release-age, which only installs versions published more than a set number of days ago, with min-release-age-exclude for packages you deliberately track closely. Microsoft's ChainDrop guidance recommends updating to npm CLI v12 and using it. A version that was live for about 100 minutes would never have been picked up by a project with a cooldown of even one day. Check npm help config for the options your npm version supports.
  • Control install scripts. --ignore-scripts is the bluntest tool and breaks packages that genuinely need to compile something. Where it is workable, use it in CI. Newer npm releases also document an allowScripts policy with npm approve-scripts, version-pinned approvals, and strict-allow-scripts to turn an unreviewed install script into a hard failure. The useful signal here is simple: 0.5.143 had no install script, and 0.5.144 suddenly did.
  • Keep secrets out of install steps. Run dependency installation in a job or step that has no deploy keys, cloud credentials or publish tokens in its environment. The loader specifically checked for CI, because that is where the valuable secrets usually are.
  • Know where every version lives. The first question in any package incident is "who has this version?" If the answer requires asking every team to run commands by hand, the response is already slow. A central inventory of every repository, image and SBOM, searchable by package and version, is what turns this into a lookup. Our post on SBOM-driven incident response covers how to set that up.

For teams that publish npm packages

These are general practices for any publisher, not an assessment of any one project.

  • Protect the release branch from everyone, including admins. Require pull requests and reviews before anything reaches the branch your release workflow builds from, and do not allow administrators to bypass the rule. A compromised maintainer account with direct push rights can otherwise feed the trusted pipeline directly.
  • Gate the publish job. Put the publish step behind a deployment environment that needs a second person to approve, and protect release tags. npm's trusted publishing documentation also describes stage-only publishing, where CI can stage a release but a maintainer must approve it with two-factor authentication.
  • Use trusted publishing and retire long-lived tokens. Trusted publishing uses short-lived OIDC tokens tied to a specific workflow, so there is no long-lived npm token sitting on a laptop or in CI for a worm to steal and reuse. npm recommends then setting the package to "Require two-factor authentication and disallow tokens." Be clear about the limit: trusted publishing makes stolen tokens less useful, but it would not by itself stop an attacker who can push to the branch the trusted workflow builds.
  • Pin GitHub Actions to full commit SHAs and keep GITHUB_TOKEN read-only by default. GitHub's security guidance recommends both. This would not have stopped a source-level compromise, but this worm family plants fake Copilot and Dependabot workflows, and pinned actions plus minimal token permissions limit what a planted or hijacked workflow can do.
  • Add a tripwire for install scripts. Tensorlake's fix PR adds a CI check that fails if a published manifest declares an install lifecycle script, and switches its workflows to npm ci --ignore-scripts. Any publisher whose package does not need install scripts can add the same kind of check in an afternoon.
  • Watch your own releases. Alert on any published version that does not trace back to a merged, reviewed pull request. When the real workflow is the publisher, an "unexpected publisher" alert stays silent.

Where Safeguard fits

Safeguard does not detect malicious packages, and it would not have flagged 0.5.144 on its own. Its role in an incident like this is inventory and response once an advisory names the bad version.

  • Find every project with the version. Safeguard builds SBOMs and dependency inventories from repositories, container images and SBOM files, including transitive dependencies, and the platform shows which projects contain a given component and version. That is the "who has tensorlake@0.5.144?" query, answered across the estate instead of repository by repository.
  • Scan locally or in CI. safeguard scan --dir . scans a working tree, --image <name> scans a container image, and --sbom <file> scans an existing SBOM. Results land as JSON under ./output, which you can search for the package and version. safeguard exec runs a single non-interactive assistant turn, which suits CI.
  • Set a policy. Policies and gates are evaluated in the platform, so once the advisory for 0.5.144 is in your findings, a gate can stop a build that still carries a flagged dependency, including one reintroduced from an old branch.
  • Fix with approval. The Safeguard CLI's assistant can edit code and run commands with your approval. You can ask it to pin tensorlake to 0.5.143 in each affected repository, regenerate the lockfile and run tests, with each command shown to you first. The /permissions modes (Ask, Accept edits, Plan, Auto, which is the default, and Strict) control how much it may do without asking. Autonomous remediation can open fix PRs, but it ships off by default.

Where Safeguard does not help: it will not find or remove gh-token-monitor, rotate your credentials, reimage a laptop, read your CI logs for the install window, or review commits that reached a release branch. Those steps belong to your incident response and your source control settings.

To try the CLI, download Safeguard and read the CLI documentation.

Common mistakes

  • Treating provenance as a safety verdict. A green provenance badge says where a package was built. In this incident it was correct and the package was still malicious.
  • Uninstalling and moving on. The worm installs persistence and edits repository files. Removing the dependency leaves both in place.
  • Revoking the GitHub token first. Several advisories warn that revocation can trigger the home directory wipe while the monitor is still running. Remove persistence, then rotate.
  • Checking only the current tree. npm ls shows today's install. A CI job that ran during the window and has since been rebuilt will not show up there; its logs and caches will.
  • Forgetting the native packages. The six tensorlake-native-*@0.5.144 packages are listed in the advisory. Include them in searches even though no payload was found in them.

FAQ

Was tensorlake 0.5.144 actually malicious, or just suspicious?

It was malicious. GitHub published a malware advisory (GHSA-rqxj-g25x-4v9v) and Tensorlake published its own critical advisory (GHSA-8g63-53c9-65j2), both on 8 October 2026. Several independent analyses describe the same credential-stealing worm.

I only imported the SDK in code that never ran. Am I safe?

No. The payload ran from a preinstall hook during npm install, so importing or running the SDK was never required. If the install ran with scripts enabled, treat that machine as compromised.

Does a valid provenance attestation mean a package is safe?

No. Provenance links a package to its source repository, commit and build workflow. npm's documentation says it does not guarantee the package has no malicious code. Here the attestation was valid because the attacker worked through the real repository and the real workflow.

Which versions should I use?

Pin to 0.5.143, which the advisory lists as unaffected, or to 0.5.145 once you have confirmed it is published. Avoid every tensorlake-native-*@0.5.144 package as well.

What should I rotate?

Everything the affected machine or CI job could reach: npm and GitHub tokens, SSH and GPG keys, cloud credentials, Kubernetes and Vault tokens, .env values and CI secrets. Remove the gh-token-monitor persistence first, then rotate from a different, clean machine.

Would a release cooldown have prevented this for consumers?

For this release, very likely. It was available for about 1 hour 40 minutes, so a project using min-release-age with even a one-day setting would not have resolved it. Cooldowns delay legitimate security fixes too, so use min-release-age-exclude for packages you deliberately update quickly.

Try it yourself

This demo shows the Safeguard CLI handling a dependency fix with your approval at each step, the same flow you would use to pin a bad package version.

Click through it at your own pace, or open it full screen.

This demo shows how to set up the local runner on your own machine, approve it with a device code, and scan from the terminal.

Click through it at your own pace, or open it full screen.

Quick check

An attacker pushes to your release branch from a compromised maintainer account and your normal CI publishes the package. Will the provenance attestation be valid?

Yes. Provenance records which repository, commit and workflow built the package, and all of those are real. It cannot tell you whether the commit was authorised. Branch protection, required reviews and an approval gate on the publish job address that gap.

You find tensorlake 0.5.144 in a CI log. What do you do before revoking the GitHub token?

Isolate the runner or host and remove the gh-token-monitor persistence service, because the advisories warn that revoking the token while it runs can trigger a wipe of the home directory. Then rotate every reachable credential from a clean machine and rebuild.

Related reading: npm takedown times during Shai-Hulud and October 2026 security incidents and what would have helped.

Sources

Last reviewed: 21 October 2026. This is a developing story; check the linked advisories for the latest status.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.