Tensorlake npm 包遭 Mini Shai-Hulud 蠕虫投毒
Tensorlake is compromised using mini Shai Hulud
安全公司 SafeDep 在 Tensorlake 的 npm 包中发现凭据窃取蠕虫 Mini Shai-Hulud,该版本在安装时即运行,npm 曾将其作为月下载量约 106,000 的包的最新版本分发。
原文完整还原了攻击者绕过 npm token、借 GitHub 网页提交与发布流程投毒的时间线,可看到供应链攻击的新路径。
We found a credential-stealing worm in [email protected]. 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.

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.
- 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 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 | Adds typescript/lib/setup.mjs and typescript/lib/Math_Symbol.js |
| 01:24:19 | c79bc475 | Adds the preinstall script |
| 01:43:23 | ad5cf5a7 | 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 | 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:
typescript/package.json @ c79bc475
DIFF 5 lines
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 by hand.
The build log shows the payload files in the tarball:
Publish to npm job, run 37706134202
TEXT 6 lines
- The release used npm trusted publishing.
- The sibling
tensorlake-native-*@0.5.144packages carry valid SLSA provenance for this run. The provenance is valid for a build of poisoned source. - The
preinstallhook 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:
Embedded loader in lib/Math_Symbol.js, decrypted
JS 15 lines
- It downloads Bun 1.3.13 from the official GitHub release.
- It runs
Math_Symbol.jswith 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:
lib/Math_Symbol.js, first line
JS 2 lines
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.
lib/Math_Symbol.js, C2 configuration (deobfuscated)
JS 2 lines
The worm posts data to hxxps://iseekaigogo[.]com:443/router. If that fails, it tries:
- Ethereum. It reads contract
0xb614155Fd88114d40549b259457Bcf921Df091B9through 35 public RPC endpoints. On 8 October the contract returnediseekaigogo.com. - Signed GitHub commits. It searches for
thebeautifulmarchoftimeand trusts only commits signed with an embedded RSA key. - 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:
lib/Math_Symbol.js, applyRemoteCode (deobfuscated)
JS 7 lines
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:
lib/Math_Symbol.js, organization check (deobfuscated)
JS 3 lines
lib/Math_Symbol.js, deletion handler (deobfuscated)
JS 3 lines
gh-token-monitorchecks 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
preinstallto 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 aRun Copilotworkflow that dumps all repository secrets, then deletes the run and branch.
The hook files and workflow match the keyv post. See also configuration files that run code.
Two bugs in the code may limit spread:
- The npm path needs
./dist/Math_Symbol.json disk and stops without it. - The planted loader looks for
ai_init.js, but the worm writesmath_init.js.
Indicators of compromise
| Type | Value |
|---|---|
| Package | [email protected], 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
来源:Hacker News · safedep.io