- Serangan rantai pasok terjadi di ekosistem NPM, dengan lebih dari 40 paket termasuk @ctrl/tinycolor yang populer disisipi malware yang dapat menyebar sendiri, sehingga rahasia di lingkungan pengembangan hingga kredensial CI/CD dapat terinfeksi secara berantai. Versi yang terinfeksi telah dihapus dari npm
- Payload serangan menjalankan bundel Webpack (
bundle.js, ~3.6MB) secara asinkron selama proses instalasi npm, lalu melakukan pengumpulan kredensial secara luas melalui variabel lingkungan, sistem file, dan cloud SDK - Logika berbahaya memaksa patch dan distribusi paket lain melalui NpmModule.updatePackage untuk penyebaran berantai, serta menyuntikkan workflow shai-hulud ke GitHub Actions untuk mencuri secret organisasi melalui
toJSON(secrets) - Data yang dikumpulkan dieksfiltrasi dengan membuat repositori GitHub publik 'Shai-Hulud', menyamar sebagai aktivitas pengembangan normal sehingga memiliki kemampuan penghindaran deteksi yang tinggi
- Dilakukan secara diam-diam melalui akses ke token dan endpoint metadata AWS/GCP/Azure/NPM/GitHub, serta pencarian secret berbasis TruffleHog
- Diperlukan tindakan seperti penghapusan paket segera, pembersihan repositori, penggantian seluruh kredensial, serta pemeriksaan log CloudTrail/GCP Audit, pemblokiran webhook, dan penerapan perlindungan branch/Secret Scanning/kebijakan cooldown
Affected Packages
- Dilaporkan ada total 195 paket/versi, termasuk @ctrl/tinycolor(4.1.1, 4.1.2), banyak paket dalam namespace @ctrl/, kelompok modul @crowdstrike/, serta ngx-bootstrap/ngx-toastr/ng2-file-upload/ngx-color di seluruh ekosistem Angular/web UI, stack mobile @nativescript-community/ dan @nstudio/, toolchain ilmu hayati teselagen/, ember-*, koa2-swagger-ui, pm2-gelf-json, wdio-web-reporter, dan lainnya
- Untuk versi yang tepat per paket, perlu merujuk ke tabel pada artikel asli dan melakukan pencocokan silang yang teliti apakah versi tersebut digunakan
- Contoh:
@ctrl/ngx-emoji-mart 9.2.1, 9.2.2,@ctrl/qbittorrent 9.7.1, 9.7.2,ngx-bootstrap 18.1.4, 19.0.3–20.0.5,ng2-file-upload 7.0.2–9.0.1dan banyak lagi
- Contoh:
Immediate Actions Required
Identify and Remove Compromised Packages
- Periksa keberadaan paket yang terinfeksi di proyek: gunakan
npm ls @ctrl/tinycolordan sejenisnya - Hapus segera paket yang terinfeksi: jalankan
npm uninstall @ctrl/tinycolordan sejenisnya - Periksa jejak lokal dengan mencari hash
bundle.jsyang sudah diketahui: gunakansha256sum | grep 46faab8a...
Clean Infected Repositories
- Hapus workflow GitHub Actions yang berbahaya: hapus
.github/workflows/shai-hulud-workflow.yml - Deteksi dan hapus branch
shai-huludyang dibuat di remote: setelahgit ls-remote ... | grep shai-hulud, jalankangit push origin --delete shai-hulud
Rotate All Credentials Immediately
- Perlu mengganti total token NPM, GitHub PAT/Actions secrets, kunci SSH, kredensial AWS/GCP/Azure, string koneksi DB, token pihak ketiga, secret CI/CD, dan lainnya
- Diperlukan rotasi penuh termasuk item yang disimpan di AWS Secrets Manager/GCP Secret Manager
Audit Cloud Infrastructure for Compromise
- AWS: di CloudTrail, periksa waktu dan pola pemanggilan
BatchGetSecretValue,ListSecrets,GetSecretValue, lalu gunakan IAM Credential Report untuk memeriksa pembuatan/penggunaan key yang tidak normal - GCP: periksa riwayat akses Secret Manager melalui Audit Logs, dan cek apakah ada event
CreateServiceAccountKey
1 komentar
Komentar Hacker News
Sebagai pengguna paket yang di-host di npm, rasanya tidak realistis untuk memantau sendiri semua dependensi beserta dependensi dari dependensi tersebut; saya juga bukan ahli TypeScript/JavaScript, jadi saya rasa tidak akan mudah menemukan malware yang disembunyikan penyerang. Belakangan ini saya memikirkan cara memperbarui dalam “mode tertunda”, yaitu hanya memperbarui ke versi yang sudah lewat masa tertentu, bukan versi terbaru. Gagasannya, jika sebuah paket sudah terekspos ke publik sekitar 6 minggu, kemungkinan besar malware sudah terungkap. Tentu ini bukan cara yang sempurna, tetapi akan bagus jika ada alat yang juga memberi opsi untuk langsung menerapkan pembaruan terbaru sebagai pengecualian ketika ada isu keamanan
Seperti yang langsung disebut dalam artikel, ada fitur bernama NPM Package Cooldown Check. Jika versi paket yang dirilis dalam rentang waktu yang ditetapkan organisasi (default 2 hari) ditambahkan ke pull request, build akan otomatis gagal. Karena sebagian besar serangan supply chain terdeteksi dalam 24 jam, masa tunggu yang sangat singkat pun bisa mengurangi paparan keamanan
Karena sulit memeriksa seluruh dependensi, saya ingin berargumen bahwa sebaiknya jumlah dependensi dikurangi sebisa mungkin dan hanya memakai paket yang terkenal serta tepercaya. Kecuali Anda berada di lingkungan yang cukup terkendali sampai bisa mempercayai semua penulisnya, mempertahankan sedikit sikap 'not-invented-here' justru pilihan yang masuk akal
Saya punya kebiasaan untuk mem-pin versi secara eksplisit di package.json dan menggunakan npm ci agar hanya versi yang tercantum di package-lock.json yang dipasang. Di CI saya menjalankan npm audit agar mendapat alarm jika ada kerentanan pada paket. Dengan begitu paket-paket hampir berada dalam keadaan “beku”, dan umur paket itu sendiri menurunkan kemungkinan terinfeksi
Dalam kasus saya, saya bahkan melangkah lebih jauh dan hanya memperbarui dependensi ketika bug benar-benar memengaruhi lingkungan penggunaan saya. Kalaupun ada kerentanan keamanan, kalau tidak berdampak saya biarkan saja. Kebanyakan developer terlalu sering memperbarui dependensi tanpa perlu, padahal seharusnya hanya dilakukan saat benar-benar dibutuhkan. Jika sebuah paket perlu sering diperbarui atau rumit, saya memilih untuk tidak memakainya sama sekali, atau saya “bekukan” menurut standar saya
Dengan memanfaatkan uv di Python, pembatasan pembaruan serupa juga bisa dilakukan. Misalnya dengan perintah
uv lock --exclude-newer $(date --iso -d "2 days ago"), versi yang dirilis dalam 2 hari terakhir bisa dikecualikanMasalah seperti ini terjadi karena paket atau versi baru tidak diawasi. Solusi terbaik adalah memisahkan distribusi stabil seperti Debian, yang hanya menerima patch keamanan dan bugfix, dari distribusi testing/unstable yang diawasi para maintainer paket. Semua pihak yang bekerja dengan repositori paket terbuka terpusat (NPM, Python, Rust, dll.) menghadapi masalah yang sama
Ada masalah pada budaya developer. Budaya memiliki ratusan dependensi (transitif) lalu memperbaruinya otomatis tanpa banyak pikir itulah masalahnya. Keputusan untuk mengekspos lingkungan build/jalankan pada begitu banyak kode pihak ketiga membawa tanggung jawab
Distribusi juga makin terbebani oleh banyaknya paket yang harus dipelihara. Sebenarnya inilah salah satu alasan ekosistem per bahasa (misalnya CPAN, Maven, RubyGems, dll.) berkembang. Distribusi Linux saja sulit menyediakan aplikasi yang diinginkan pengguna, sehingga muncul berbagai jalur seperti freshmeat, linuxbrew, flatpak, PPA, dan lainnya. Saya rasa tidak ada komunitas yang punya kapasitas untuk mengawasi dan mendukung banyak cabang dari begitu banyak library yang beragam
Sebagai developer Debian, makin banyak “noise” sebelum kode upstream diadopsi—terutama perubahan gaya semata dan pembaruan tooling—membuat perubahan yang benar-benar penting jadi terlalu sulit dideteksi. Saya berharap perubahan seperti ini ditahan kecuali memang berupa refactor, perbaikan bug, penambahan fitur, atau hasil tooling untuk mencari kode bermasalah yang benar-benar perlu ditinjau manusia
Di Rust ada sistem bernama cargo vet. Perusahaan seperti Google dan Mozilla ikut berpartisipasi untuk berbagi dan memverifikasi paket secara otomatis
Menurut saya ada cara untuk tetap terdesentralisasi sambil menambahkan pagar pengaman tertentu. Misalnya, paket di atas skala tertentu harus disetujui dua akun yang memakai 2FA, atau paket populer hanya boleh diunggah ke npm lewat sistem reproducible build. Ini tidak berarti harus meninggalkan desentralisasi sepenuhnya, hanya menambah sedikit kerja ekstra untuk proyek besar
Karena serangan supply chain yang terus berlanjut belakangan ini, saya jadi lebih serius mempertimbangkan server rendering tanpa JavaScript. Berkat HTMX, saya sadar kita bisa melangkah sangat jauh bahkan tanpa JavaScript, dan pendekatan ini tampaknya juga bisa membuat aplikasi lebih cepat dan lebih stabil
Saya ingin menekankan bahwa lingkungan JS tradisional sebenarnya adalah sandbox paling aman. Selama hampir 30 tahun, kode JS tak tepercaya dijalankan di miliaran perangkat, tetapi contoh serangan besar yang berhasil terhadap browser engine bisa dihitung dengan jari. Namun lingkungan NodeJS dan npm perlu didesain ulang total dari sisi keamanan. Insiden seperti leftpad berakar dari budaya mengunggah bahkan snippet kode sederhana ke npm
Aneh rasanya kalau serangan seperti ini otomatis dipersempit menjadi isu lingkungan tertentu (JavaScript). Justru masalah yang lebih besar adalah bahwa bahkan penguatan keamanan yang sudah tersedia di npm pun sama sekali belum diterapkan pada lingkungan lain (PyPI, Crates, dll.)
Vendoring bisa mengurangi paparan, tetapi saya menilai itu bukan solusi akar masalah. Jika NPM serius soal keamanan, mereka harus mewajibkan 2FA dan pemindaian paket sebelum publish, bahkan memaksa penandatanganan dengan hardware key. semver atau CRC saja tidak cukup. Semua ini seharusnya tersedia bawaan di dalam sistem manajemen paket
Sebenarnya serangan seperti ini bukan masalah JavaScript saja, melainkan karena developer tidak cukup mengawasi saat menambahkan dependensi baru. Ini bisa diterapkan persis sama pada ekosistem bahasa lain seperti Rust atau Go
Semua bahasa yang sangat bergantung pada package manager dan punya standard library yang miskin sama-sama rentan. Dalam jangka panjang saya merasa perlu kembali ke vanilla JavaScript. Rust juga punya tingkat ketergantungan paket yang tinggi. Justru Go tampak seperti contoh yang patut dijadikan acuan dalam hal ini
Saya rasa dibutuhkan sistem yang bisa melacak kode ringan yang menandatangani commit/rilis dengan kunci tepercaya, lalu memasang dan memverifikasinya. Sudah ada pendekatan provenance npm dengan sigstore, tetapi sejauh ini tampaknya belum dipakai luas dan masih terbatas pada verifikasi penerbit
Kerentanan ini sebenarnya sudah dilaporkan ke NPM pada 2016 (imbauan CERT), tetapi jawaban NPM saat itu adalah WAI (working as intended)
Bagi yang tidak tahu arti WAI: biasanya itu singkatan dari “working as intended”
Bahkan jika skrip postinstall sama sekali tidak ada, saya rasa malware pada akhirnya tetap akan berjalan saat modul diimpor selama proses build, saat server dijalankan, saat testing, dan seterusnya. Pada akhirnya selalu ada momen setelah npm install ketika sesuatu benar-benar dijalankan...
Ini mengingatkan saya pada komentar yang saya lihat di sini saat insiden left-pad, tentang seorang maintainer npm ternama yang memiliki 600 paket npm dan 1.200 baris kode JavaScript. Contoh yang ingin saya angkat adalah esbuild, yang hampir tidak punya dependensi eksternal dan hanya memakai standard library Go
Proyek lain yang disebut “generasi berikutnya” juga punya rantai dependensi yang cukup kecil jika dilihat dari biomejs dan swc. Namun jika melihat kode sumber Rust aslinya, biomejs dan swc pada akhirnya juga memiliki banyak dependensi. Jika proyek seperti ini makin menyebar, saya menduga ekosistem cargo akan menempuh jalan yang sama. Kalau ada yang tahu proyek besar yang ditulis dengan gaya seketat esbuild, saya ingin mendapat rekomendasi
Salah satu alasan saya pindah ke Go adalah tren library purego. Biasanya hanya bergantung pada standard library dan golang.org/x, bisa dikompilasi tanpa CGO, dan sangat portabel.
go mod vendorbisa membantu mengelola risiko jangka pendek, tetapi bukan solusi mendasar. Go juga tidak menyediakan verifikasi paket end-to-end (seperti tanda tangan/pengecekan kunci), jadi tetap ada celah. Khususnya karena banyak fokus tertuju pada infrastruktur CI/CD, saya rasa keamanan bisa ditingkatkan bila build dan distribusi dimungkinkan tanpa perlu menyerahkan signing key. Package manager seharusnya mendorong tanda tangan GPG, dan distribusi juga sebaiknya dilakukan dengan commit git yang ditandatanganiKasus eslint terasa sangat membuat frustrasi. Jika melihat graf dependensi, ukurannya luar biasa besar, dan jika maintainer tidak memprioritaskan pengurangan dependensi, pada akhirnya satu-satunya jalan adalah beralih ke solusi lain (oxlint)
Solusinya adalah membuat sendiri fitur yang mudah dan mengurangi dependensi eksternal. Biasanya dengan cara ini saja 2/3 dari keseluruhan dependensi bisa dipangkas. Hal-hal sederhana seperti left-pad sebaiknya dibuat sendiri dan dijaga tetap dalam kendali dengan unit kecil serta test, karena beban pengelolaannya juga tidak terlalu besar. Dependensi yang tidak perlu harus dipangkas tanpa ragu
Yang tercantum di root Cargo.toml proyek Rust adalah untuk seluruh workspace, dan dependensi tiap crate sebenarnya jauh lebih dangkal. Perlu melihat lebih dalam untuk memahami struktur dependensi yang sesungguhnya
Kekurangannya adalah sekarang untuk meninjau proyek JavaScript, kita juga harus bisa membaca Golang. Ditambah lagi ada
node install.jsyang dijalankan lewat post-install, jadi pada akhirnya pilihannya hanya benar-benar percaya atau membaca semua kodenyaSulit dipercaya bahwa npm masih mengeksekusi skrip postinstall dari semua dependensi secara default. Pnpm dan Bun hanya menjalankannya bila masuk allowlist, sedangkan Composer bahkan tidak menjalankan lifecycle script untuk dependensi sama sekali. Mengingat risiko yang dibawa paket dependensi di lingkungan build maupun development, pendekatan seperti ini menurut saya lebih aman
Saya penasaran mengapa serangan besar seperti ini tidak terlalu sering terdengar pada package manager lain (misalnya Rust build.rs, Python, Java, dll.). Bukan hanya postinstall, secara prinsip hampir semua ekosistem memungkinkan hal semacam ini, tetapi kejadian tampaknya terkonsentrasi di npm
Saya melihat default Pnpm berubah menjadi memblokir skrip, dan saya penasaran dengan reaksi komunitas—misalnya soal pengalaman penggunaan, atau penyalahgunaan perintah allow—serta bahwa komunitas packaging Python juga sedang menjalankan diskusi serupa terkait wheel variants. Saya ingin belajar dari pengalaman ekosistem lain
Serangan kali ini meluas ke lebih dari 180 paket; lihat blog Aikido Security
Saya penasaran siapa yang pertama kali menemukan serangan ini. Menarik melihat bagaimana tiap blog memberi kredit dengan cara berbeda. Aikido berkata “kami menemukan serangan besar”, sementara Socket, Ox, Safety, Phoenix, Semgrep, dan lainnya juga menjelaskannya dengan versi masing-masing
Saya Mackenzie dari Aikido. Orang pertama yang melaporkan kasus ini adalah developer Daniel Pereira; beliau meneruskannya ke Socket, dan Socket yang pertama menganalisis 40 paket awal beserta malware-nya. Setelah itu Aikido menemukan tambahan 147 paket dan juga paket Crowdstrike. Yang pertama menyadari bahwa malware ini adalah worm yang menyebar sendiri sebenarnya adalah Step. Menarik bahwa banyak organisasi secara independen memainkan peran yang berbeda-beda
Tampaknya beberapa developer menemukannya hampir pada waktu yang sama, dan Step serta Socket masing-masing menyebut orang yang berbeda. Pada akhirnya para vendor keamanan di industri mendeteksinya dengan pendekatan mereka masing-masing, seperti analisis kode AI (Socket, Aikido) atau pemantauan pipeline eBPF (Step)
Kalau begitu banyak vendor mendeteksinya secara independen, saya jadi bertanya-tanya apakah teknologinya tidak sebaiknya langsung dibagikan ke npm agar pendaftaran paket berbahaya bisa diblokir sejak awal. Kalau begitu vendor tidak bisa lagi menjual sistem peringatan dini, jadi mungkin itulah sebabnya mereka tidak melakukannya
Artikel OP mengutip langsung kalimat bahwa “@franky47 menemukan fenomena ini lalu segera memberi tahu komunitas melalui isu GitHub”
Saya pikir nama yang dipilih penyerang, 'Shai Hulud', cukup cerdik: nama cacing raksasa dipakai untuk malware worm sungguhan. bundle.js utamanya juga raksasa, 3.6MB. Bahkan varian malware-nya pun membengkak secara sangat khas npm
Saya punya firasat bahwa sebentar lagi satu serangan supply chain akan secara tak sengaja memicu serangan supply chain lainnya
Malware juga mengikuti hukum Moore; virus tequila pada 1991 berukuran 2.6KB, sekarang ukurannya sudah beberapa MB