Skip to main content
BlogSecurity

Kompromi npm Tensorlake Shai-Hulud: Kunci Install Script

Varian Shai-Hulud menyerang npm SDK Tensorlake lewat hook preinstall dan workflow rilis proyek itu sendiri. Apa yang dicuri dan cara memblokirnya.

Matthews Wong

Software Engineer

Oktober 202612 menit baca
Kompromi npm Tensorlake Shai-Hulud: Kunci Install Script

Ringkasan Utama

Versi 0.5.144 SDK npm Tensorlake membawa hook preinstall yang menjalankan worm Shai-Hulud pencuri kredensial, dirilis lewat workflow proyek sendiri dengan provenance yang valid. Pertahanannya: blokir install script dependency secara default, pasang cooldown usia rilis, dan hapus persistence gh-token-monitor sebelum mencabut token GitHub apa pun.

Detail paling ganjil dari insiden ini adalah produknya. Tensorlake menjual platform cloud untuk menjalankan AI agent yang terisolasi dan kode buatan AI yang tidak tepercaya, dan SDK npm-nya justru menjalankan kode tidak tepercaya di mesin developer, di luar sandbox mana pun, begitu seseorang mengetik npm install.

Tulisan ini adalah pembacaan cermat atas tiga write-up primer: analisis payload dari SafeDep, laporan The Register, dan pull request revert dari Tensorlake sendiri, ditambah dokumentasi npm dan pnpm untuk kontrol yang saya sarankan. Saya tidak menjalankan payload dan tidak memulihkan binary apa pun, jadi bila sebuah klaim hanyalah inferensi SafeDep, saya sebutkan. Tidak ada exploit yang berfungsi di sini, hanya hal yang dibutuhkan defender.

Apa yang dirilis di tensorlake 0.5.144

Versi 0.5.144 package npm tensorlake berbahaya, dan 0.5.145 adalah perbaikannya. Sumber berbeda soal popularitas package ini: The Register menyebut sekitar 12.000 download per minggu, sedangkan SafeDep menyebut sekitar 106.000 per bulan. Itu jendela waktu yang berbeda, bukan kontradiksi sungguhan. Yang jelas ini SDK berukuran menengah, bukan nama yang dikenal semua orang.

Deteksinya cepat. SafeDep mengatakan npm mencatat publish pada 01:12:07 UTC dan ia menandai versi itu pada 01:20, delapan menit kemudian. The Register mengaitkan angka 11 menit dengan engine Socket. Saya tidak bisa membaca postingan Socket sendiri (fetch saya mendapat 403), jadi saya mengutip kedua angka apa adanya dan tidak mencoba menyamakannya.

Bagian yang seharusnya mengkhawatirkan maintainer adalah cara masuknya. SafeDep mencatat delapan commit lewat antarmuka web GitHub, semuanya berstatus verified, berakhir pada version bump. PR revert Tensorlake menyebut versi berbahaya itu di-commit langsung ke main dan dirilis lewat workflow publish_npm.yaml. Sumber-sumber itu tidak menjelaskan bagaimana akun administrator diambil alih, dan saya tidak akan menebak.

Jalur rilis adalah titik lemahnya

Temuan SafeDep: penyerang tidak butuh npm token. Akun administrator repositori menjalankan workflow publish proyek secara manual, dan rilis memakai npm trusted publishing. Package tensorlake-native bersaudara di versi 0.5.144 membawa provenance SLSA yang valid untuk run itu, jadi provenance-nya asli sementara source yang dideskripsikannya sudah teracuni.

Itu bukan cacat pada provenance. Dokumentasi npm menggambarkannya sebagai bukti kriptografis tentang di mana dan bagaimana package dibangun, dan menggambarkan trusted publishing sebagai penghapus token berumur panjang lewat kredensial OIDC berumur pendek yang spesifik untuk satu workflow. Keduanya bekerja sebagaimana mestinya. Tak satu pun mengklaim source tree-nya aman, padahal itulah yang diam-diam diasumsikan banyak tim ketika melihat badge provenance hijau.

PR revert Tensorlake menunjukkan apa yang diubah maintainer: me-revert semua commit setelah commit bersih terakhir, mengganti tiga workflow ke npm ci --ignore-scripts, menambah tripwire yang berjalan sebelum instalasi dependency, dan menaikkan versi ke 0.5.145. Pengaturan repositori yang diubah di luar PR mencakup branch protection dan reviewer pada environment npm. Menurut saya, reviewer kedua yang wajib pada environment rilis adalah kontrol yang akan mengubah satu akun admin yang dibajak menjadi job yang terblokir, tetapi itu inferensi saya, bukan klaim sumber.

Hook preinstall dan apa yang dicuri worm

Pintu masuknya adalah lifecycle script saat instalasi, yang dijalankan npm otomatis dengan izin siapa pun yang menjalankan install. Peringatan Socket, seperti dikutip The Register, adalah bahwa ini terjadi di mesin developer atau build server, di luar sandbox Tensorlake, sebelum kode buatan AI mana pun dijalankan.

// The shape of the hook, as SafeDep describes it. package.json in 0.5.144:
//   "scripts": { "preinstall": "node lib/setup.mjs" }
//
// What lib/setup.mjs did, in order (SafeDep's analysis, not my reproduction):
//   1. exit early if CI, GITHUB_ACTIONS, GITLAB_CI or RUNNER_ENVIRONMENT is set
//   2. download Bun 1.3.13 from the official GitHub release
//   3. run lib/Math_Symbol.js (856,501 bytes) with that Bun
//
// Step 1 is why "our CI install was clean" proves nothing. The hook was
// written to skip the build server and run on the laptop.
//
// Check your own tree for the three files SafeDep names:
//   lib/setup.mjs   32,645 bytes
//   lib/Math_Symbol.js   sha256 b50a00900399ba99fb6ce1fc151519cb99d44320ef2a631f2237e1aea0ad6fec
//   math_init.js   (written by the worm into repos it touches)

Daftar target pengumpulan data dari SafeDep panjang. Saya kelompokkan berdasarkan apa yang harus Anda rotasi. Kolom kedua adalah deskripsi SafeDep, kolom ketiga adalah saran saya, bukan milik mereka.

TargetYang dibaca menurut SafeDepRespons defender
File di diskSSH key, config cloud CLI, .npmrc, .env, file Docker dan Kubernetes, ~/.claude.json, ~/.kiro/settings/mcp.json, shell historyRotasi semua kredensial yang tersimpan di file itu, termasuk token MCP dan tool AI
Wallet kriptowallet.dat, Exodus, Electrum, Ledger Live, ekstensi browser seperti MetaMask dan PhantomPindahkan dana ke wallet baru yang dibuat dari mesin yang bersih
Password browserBinary yang diunduh dan dijalankan dengan argumen dump password; SafeDep menduga itu HackBrowserData tetapi tidak berhasil memulihkannyaAnggap semua login tersimpan di browser sudah terbuka, lalu ganti
AWSSTS, SSM Parameter Store, Secrets ManagerRotasi key, lalu tinjau CloudTrail untuk akses ke layanan itu
GitHub ActionsSecret yang dibaca dari memori proses Runner.WorkerRotasi secret repositori dan organisasi, tetapi hanya setelah token monitor dihapus
Kubernetes dan VaultToken service account dan token VaultCabut dan terbitkan ulang, lalu periksa audit log untuk pemakaian dari alamat asing

Command and control dirancang agar tahan takedown. SafeDep melaporkan endpoint utama, fallback yang membaca contract Ethereum lewat 35 endpoint RPC publik, fallback yang hanya memercayai commit GitHub bertanda tangan RSA key tertanam, dan repositori dead-drop terenkripsi dengan deskripsi 'Shai-Hulud: Here We Go Again'. Setelah exfiltration, ia melakukan polling tiap 45 sampai 90 detik dan bisa menjalankan kode yang dikirim server. Memblokir satu domain tidak mengakhiri insiden.

Token monitor yang menghukum pencabutan token

SafeDep dan The Register sama-sama menjelaskan perilaku paling jahat. Worm memasang watcher yang memeriksa token GitHub curian, dan jika token berhenti berfungsi, ia menghapus home directory pengguna yang terinfeksi. SafeDep merinci mekanismenya: watcher hanya aktif untuk token yang akunnya tidak punya organisasi, memeriksa tiap 60 detik selama 24 jam, dan pada respons 40x dari GitHub menjalankan penghapusan rekursif home directory.

SafeDep menyebut nama persistence-nya: systemd user service bernama gh-token-monitor.service di Linux, LaunchAgent berlabel com.user.gh-token-monitor di macOS, dan scheduled task bernama gh-token-monitor di Windows. The Register meneruskan saran peneliti untuk menonaktifkan monitor sebelum mencabut kredensial, dan SafeDep mengatakan hal yang sama dengan lugas.

Urutan lebih penting daripada kecepatan. Temukan dan hapus gh-token-monitor di setiap host terdampak sebelum Anda mencabut satu pun token GitHub. Mencabut lebih dulu bisa memicu penghapusan. Jika monitor tidak ditemukan, bekerjalah dari mesin yang dibangun ulang, seperti yang disarankan Socket dalam laporan The Register.

# Read-only discovery first. Nothing here revokes a token.

# Linux: the systemd user service and its files (names from SafeDep)
systemctl --user list-unit-files | grep gh-token-monitor
ls -la ~/.local/bin/gh-token-monitor.sh ~/.config/gh-token-monitor/ 2>/dev/null

# macOS: the LaunchAgent label
launchctl list | grep com.user.gh-token-monitor

# Only after you have found it, stop it. THEN rotate GitHub tokens.
systemctl --user disable --now gh-token-monitor.service      # Linux
launchctl remove com.user.gh-token-monitor                    # macOS

# Windows (PowerShell): scheduled task named gh-token-monitor
#   Get-ScheduledTask -TaskName gh-token-monitor

# Is the package, or its files, anywhere in this checkout?
grep -n '"tensorlake"' package-lock.json | head
find . -path ./node_modules/.cache -prune -o \
  \( -name Math_Symbol.js -o -name math_init.js \) -print
grep -rl "WORMTAG" --include=*.js --include=*.mjs . 2>/dev/null | head

Perintah di bawah hanya membaca sampai baris stop. Saya menulisnya dari nama artefak yang dipublikasikan SafeDep, dan belum menjalankannya pada infeksi nyata, jadi cocokkan nama dengan daftar indikator SafeDep sebelum mengandalkan hasil negatif.

Garis keturunan ChainDrop dan alasan scanner melewatkannya

The Register menyebut analisis mengaitkan rilis Tensorlake dengan ChainDrop, varian Shai-Hulud yang pertama terlihat pada 4 Agustus 2026. Kampanye itu menginfeksi 444 package dari beberapa publisher yang bersama-sama diunduh sekitar 2 miliar kali per bulan, termasuk keyv, flat-cache, dan cache-manager. SafeDep menambah kaitan konkret: payload Tensorlake memakai salt cipher yang sama dengan payload keyv.

Dua ciri penting bagi defender. Pertama, ChainDrop menyebar lewat tarball npm yang dibangun ulang, sehingga membaca source repositori bisa tidak menunjukkan apa-apa. Kedua, ia menanam startup hook di konfigurasi repositori, dan The Register menyebut sekadar membuka branch terinfeksi di VS Code atau Claude Code bisa mengaktifkannya. SafeDep melaporkan varian Tensorlake meng-commit hook Claude Code dan VS Code dengan author bernama claude serta menanam workflow berjudul 'Run Copilot' yang mengeluarkan secret repositori lalu menghapus run dan branch-nya.

# Look for repository config you did not write, on EVERY branch.
# Checking only main is the mistake the ChainDrop advice warns about.
git fetch --all --prune
for ref in $(git for-each-ref --format='%(refname)' refs/remotes); do
  git ls-tree -r --name-only "$ref" \
    | grep -E '^(\.claude/settings\.json|\.vscode/tasks\.json)$' \
    | sed "s|^|$ref  |"
done

# SafeDep also lists a planted branch name and a planted workflow title.
git branch -r | grep 'dependabot/github_actions/format/setup-formatter'
git grep -n "Run Copilot" -- .github/workflows

Abby Kearns dari ActiveState, yang dikutip dalam berita Agustus The Register, menyarankan memeriksa setiap branch untuk file .claude/settings.json dan .vscode/tasks.json yang tidak Anda buat, dan memperlakukan konfigurasi dari repositori sebagai konten yang dapat dieksekusi. Ia juga menyebutnya kampanye pertama yang menyadari celah pada dependency scanning dan memakainya dalam skala besar. SafeDep mencatat worm ini punya bug yang mungkin membatasi penyebarannya, misalnya jalur npm berhenti jika file yang diharapkan tidak ada. Saya tidak akan berharap bug itu bertahan.

Mengunci install script

Perbaikan termurah adalah yang diterapkan Tensorlake pada pipeline-nya sendiri: jangan biarkan dependency menjalankan kode saat instalasi kecuali Anda memilihnya. Tabel memetakan tiap kontrol ke perilaku yang terdokumentasi dan ke dampaknya pada hook ini. Kolom terakhir adalah penalaran saya, bukan hasil uji.

KontrolSettingPerilaku terdokumentasiEfek pada insiden ini (penalaran saya)
npm: lewati lifecycle scriptignore-scripts=trueJika true, npm tidak menjalankan script yang ditentukan di package.json. Default false.Hook preinstall tidak pernah mulai, jadi tidak ada loader, unduhan Bun, atau payload
pnpm: allowlist buildallowBuilds, strictDepBuildsPackage yang tidak terdaftar dianggap unreviewed; strictDepBuilds, default true, keluar dengan kode non-nol bila ada build script yang belum ditinjauVersi baru package yang tidak terdaftar menggagalkan install, bukan menjalankan hook-nya
Cooldown usia rilismin-release-age, minimumReleaseAgenpm mendokumentasikan min-release-age dalam hari, default null. pnpm mendokumentasikan minimumReleaseAge dalam menit, default 1440 sejak v11 dan 0 sebelumnyaHanya membantu jika versi buruk ditarik sebelum cooldown habis. Deteksi hanya butuh menit di sini, tetapi saya tidak menemukan waktu penarikannya
Install CI tanpa scriptnpm ci --ignore-scriptsFlag yang ditambahkan PR Tensorlake ke publish_npm.yaml, typescript_sdk.yaml, dan tests.yamlMelindungi runner build dan rilis, yang memang dilewati loader; laptop tetap butuh setting .npmrc
Trusted publishing plus environment terproteksiid-token: write, nama environment opsionalDokumen npm: token OIDC berumur pendek; field environment opsional dan ditujukan untuk deployment protectionTidak menghentikan instalasi. Bisa menaruh persetujuan manusia di antara akun admin yang dibajak dan sebuah rilis
# ---- .npmrc (npm) ----
# Do not run any package's lifecycle scripts during install.
ignore-scripts=true
# Only resolve versions published more than N days ago. The npm CLI v11
# config page documents this key in days; run "npm config get min-release-age"
# on your own npm to confirm it is recognised before you rely on it.
min-release-age=3

# ---- pnpm-workspace.yaml (pnpm) ----
# Minutes. The pnpm docs give 1440 (one day) as the default since v11, 0 before.
minimumReleaseAge: 1440
# Build scripts are an allowlist. Unlisted packages are "unreviewed".
allowBuilds:
  esbuild: true
# Default true: exit non-zero when a dependency has an unreviewed build script.
strictDepBuilds: true

PR Tensorlake juga menambah tripwire yang menggagalkan CI jika manifest yang dipublikasikan mendeklarasikan install lifecycle script atau jika folder lib SDK berisi file tak terduga. Saya belum melihat script mereka, hanya deskripsinya. Sketsa di bawah adalah versi saya sendiri dari ide yang sama, memakai field package-lock.json v3, dan tidak butuh jaringan.

// scripts/check-no-install-scripts.mjs
// My own sketch of the tripwire idea from Tensorlake's revert PR (their script
// is described there, not reproduced here). Run it BEFORE any install.
// package-lock.json v3 marks every package that ships an install-time script
// with hasInstallScript: true, so this needs no network and no install.
import { readFileSync } from "node:fs";

// Packages you have reviewed and accept running build steps for.
const ALLOWED = new Set(["node_modules/esbuild"]);

const lock = JSON.parse(readFileSync("package-lock.json", "utf8"));
const offenders = Object.entries(lock.packages ?? {})
  .filter(([path, meta]) => path !== "" && meta.hasInstallScript)
  .map(([path]) => path)
  .filter((path) => !ALLOWED.has(path));

// Your own manifest should not declare install-time hooks either: that is
// exactly where the 0.5.144 payload was attached.
const own = JSON.parse(readFileSync("package.json", "utf8")).scripts ?? {};
const ownHooks = ["preinstall", "install", "postinstall"].filter((h) => h in own);

if (offenders.length || ownHooks.length) {
  console.error("Unreviewed install scripts:", offenders.concat(ownHooks));
  process.exit(1);
}
console.log("No unreviewed install scripts.");
# .github/workflows/ci.yml (fragment)
permissions:
  contents: read          # no id-token: write on jobs that only build and test
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: node scripts/check-no-install-scripts.mjs   # before the install
      - run: npm ci --ignore-scripts                     # as in Tensorlake's PR
      - run: npm test

Kedua bagian saling melengkapi. Setting .npmrc melindungi laptop, tempat worm ini ingin berjalan. Fragmen CI dan tripwire melindungi pipeline dan memberi Anda check yang gagal pada hari seseorang menambah dependency dengan install script.

Perlakukan juga file tooling AI sebagai secret. SafeDep mencantumkan ~/.claude.json dan ~/.kiro/settings/mcp.json di antara file yang dibacanya. Jika token MCP server tersimpan di sana, masukkan ke daftar rotasi dan, lebih baik lagi, ke secret manager dengan masa berlaku pendek.

Yang rusak, dan yang tidak diselesaikan ini

Mematikan install script memang merusak beberapa hal. Package yang meng-compile kode native atau mengunduh binary saat instalasi, esbuild contoh umumnya, akan gagal atau terpasang tidak lengkap sampai Anda memasukkannya ke allowlist. Di pnpm itu daftar pendek yang ditinjau sekali; di npm biasa, ignore-scripts=true bersifat semua atau tidak sama sekali, jadi siapkan beberapa langkah build yang dijalankan eksplisit. Biayanya nyata, dan itu alasan banyak tim tidak pernah mengaktifkannya.

Cooldown menukar kesegaran dengan keamanan. Penundaan satu atau tiga hari juga menunda patch keamanan yang sah, jadi simpan daftar pengecualian untuk package yang perbaikannya Anda mau segera, dan jangan pernah memakai cooldown sebagai pengganti membaca advisory. Dokumen npm dan pnpm sama-sama menyediakan setting pengecualian untuk tujuan itu.

Kegagalan yang lebih dalam adalah yang dimanfaatkan loader: ia keluar saat berjalan di CI. Pipeline hijau tidak mengatakan apa-apa tentang laptop developer yang menyimpan SSH key, kredensial cloud, dan token tool AI. Dan tak satu pun setting ini membantu setelah akun admin maintainer diambil alih, karena rilis beracun itu dibangun secara sah. Itu butuh branch protection, reviewer wajib pada environment rilis, dan sedikit orang dengan hak admin.

Yang dikerjakan hari Senin

Kerjakan berurutan. Urutannya disengaja, karena langkah ketiga adalah yang bisa menghapus home directory.

  1. Cari tensorlake versi 0.5.144 di setiap lockfile dan mesin, lalu pindah ke 0.5.145 atau lebih baru; jika 0.5.144 pernah terpasang, anggap mesin itu terkompromi dan bangun ulang, jangan sekadar dibersihkan.
  2. Cari artefak yang didaftar SafeDep: lib/setup.mjs, lib/Math_Symbol.js, math_init.js, service, agent atau task gh-token-monitor, dan branch dependabot yang ditanam.
  3. Hentikan gh-token-monitor di setiap host terdampak sebelum mencabut token GitHub apa pun, lalu rotasi kredensial GitHub, npm, cloud, SSH, Kubernetes, dan Vault dari mesin yang bersih.
  4. Audit GitHub untuk .claude/settings.json dan .vscode/tasks.json di setiap branch, workflow berjudul Run Copilot, dan repositori publik asing di bawah akun Anda.
  5. Tambahkan ignore-scripts=true ke .npmrc atau allowlist pnpm, plus cooldown usia rilis dengan daftar pengecualian pendek, dan jalankan npm ci --ignore-scripts di CI.
  6. Pasang branch protection di default branch, wajibkan reviewer pada environment rilis npm, dan kurangi jumlah administrator repositori.

Jika Anda memelihara package sendiri, periksa juga package terpublikasi mana yang bisa dijangkau token Anda. SafeDep melaporkan worm menaikkan patch version setiap package yang bisa dipublish sebuah npm token, jadi blast radius satu token adalah semua package yang dapat dipublish-nya.

Aturan yang saya ambil: install script sebuah dependency adalah kode yang tidak Anda tinjau yang berjalan dengan izin Anda, jadi tolak secara default, izinkan sebagai pengecualian, dan jangan pernah menganggap CI hijau atau badge provenance valid sebagai bukti package itu aman.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Versi npm Tensorlake mana yang berbahaya?

Versi 0.5.144 package npm tensorlake berbahaya, dan 0.5.145 adalah rilis perbaikannya. SafeDep melaporkan npm menyajikan 0.5.144 sebagai versi latest. Jika pernah terpasang di sebuah mesin, anggap mesin itu terkompromi dan bangun ulang.

Mengapa malware ini melewati runner CI?

SafeDep melaporkan loader memeriksa variabel CI, GITHUB_ACTIONS, GITLAB_CI, dan RUNNER_ENVIRONMENT lalu keluar jika ada. Artinya instalasi CI yang bersih tidak membuktikan apa-apa soal laptop developer, yang menyimpan SSH key, kredensial cloud, dan token tool AI yang diincar worm.

Apakah saya harus langsung mencabut token GitHub setelah terinfeksi?

Jangan sebelum persistence dihapus. SafeDep dan The Register sama-sama memperingatkan bahwa watcher gh-token-monitor bisa menghapus home directory saat token GitHub curian berhenti berfungsi. Temukan dan nonaktifkan service, LaunchAgent, atau scheduled task itu dulu, lalu rotasi kredensial dari mesin yang bersih.

Bagaimana cara mencegah dependency npm menjalankan install script?

Setel ignore-scripts=true di .npmrc dan jalankan npm ci --ignore-scripts di CI. Di pnpm, biarkan strictDepBuilds aktif dan daftarkan hanya package yang sudah ditinjau di allowBuilds. Siapkan entri allowlist eksplisit untuk package yang meng-compile atau mengunduh binary saat instalasi.

Apakah npm trusted publishing atau provenance mencegah serangan ini?

Tidak. SafeDep menemukan rilis beracun itu memakai trusted publishing dan package bersaudaranya membawa provenance SLSA yang valid, karena build berjalan sah di atas source yang sudah teracuni. Provenance membuktikan di mana package dibangun, bukan bahwa source-nya aman. Branch protection dan reviewer wajib pada environment rilis menutup celah itu.