Software Supply Chain Security: Lessons From Almost Shipping a Compromised Dependency
Cerita gue soal malam di mana gue hampir nge-ship dependency yang disusupi attacker, dan perjalanan gue belajar ngamankan supply chain software: dari lockfile, scanner, SBOM, sampai signing image biar gak ada lagi kode jahat yang nyamar di pipeline.
Jumat Malam yang Bikin Gue Ngeluh ke Kopi
Malam Jumat itu gue duduk sendirian di depan laptop, nungguin pipeline CI muter. Semua item checklist rilis udah beres. Kode udah di-review, test lolos, tinggal nunggu image kebuild terus naik ke staging. Eh, tiba-tiba di log muncul satu baris yang bikin alis gue naik. Ada dependensi baru yang masuk ke lockfile padahal gue gak nambahin apa-apa. Gue cek commit history, dan ternyata ada satu PR "security update" dari bot yang ke-merge duluan tanpa gue baca bener. Isinya cuma ngebump satu package kecil, tapi pas gue buka isinya, package itu ternyata fork dari package asli yang namanya mirip banget, cuma beda satu huruf. Typosquatting. Gue langsung freeze.
Untungnya waktu itu kebiasaan gue selalu baca diff dulu sebelum deploy ke produksi, dan build-nya gue batalkan. Tapi bayangin kalau gue gak sempat lihat. Satu package kecil, satu baris kode jahat, dan seluruh aplikasi bisa kompromi. Dari malam itu gue mutusin buat serius belajar soal supply chain security, dan tulisan ini adalah rangkuman perjalanan gue: bukan teori dari buku, tapi hal-hal yang beneran gue terapin di project nyata, plus beberapa kegagalan yang bikin gue malu sendiri.

Kenapa Supply Chain Jadi Medan Perang Baru
Gue mulai dari pertanyaan paling dasar: kenapa sih attacker gak langsung nyerang aplikasi gue, tapi malah nyerang package yang gue pakai? Jawabannya gampang: karena nyerang satu package lebih efisien daripada nyerang seribu aplikasi. Kalau gue berhasil nyuntik kode jahat ke sebuah library yang dipakai ribuan project, gue dapet akses ke semua project itu sekaligus tanpa perlu exploit satu-satu. Ini model serangan yang namanya supply chain attack, dan contohnya udah banyak banget.
Gue inget kejadian ua-parser-js di awal 2022. Library parser user-agent yang dipake hampir di mana-mana itu sempet diisi malware di salah satu versinya, dan karena banyak developer yang auto-update tanpa mikir, malware-nya nyebar ke ribuan project dalam hitungan jam. Ada juga event-stream, package yang dulu populer banget, yang disusupi attacker lewat akses maintainer yang udah gak aktif. Bahkan yang paling serem buat gue: xz-utils. Backdoor di library kompresi yang udah ada di hampir semua distro Linux itu ditemukan hampir telat, dan pelakunya udah berusaha selama bertahun-tahun biar kodenya diterima. Ini bukan cerita horor yang jauh dari kita. Ini hal yang bisa happen ke project kecil sekalipun, karena attacker gak milih target berdasarkan seberapa besar project-nya, tapi seberapa dalam kepercayaan yang bisa mereka manfaatkan.
Nah, dari situ gue sadar satu hal: kode yang gue tulis sendiri itu cuma sebagian kecil dari apa yang gue deploy. Sebagian besar isi image produksi gue adalah kode orang lain: dependencies, base image, tools, dan semua layer yang gue gak tulis dengan tangan. Dan selama gue gak ngerti apa yang masuk ke image itu, gue sebenernya lagi jalan di atas es tipis.
Peta Masalahnya: Bukan Cuma Soal Dependency
Salah satu kesalahan gue di awal adalah mikir kalau supply chain security itu cuma soal nge-scan dependencies. Padahal pas gue mulai petakan sendiri, ada lima pintu masuk yang harus gue perhatiin, dan semuanya pernah bikin gue keluar keringat dingin.
Pertama, dependencies langsung dan transitif. Ini yang paling kelihatan, tapi justru yang paling sering disepelein. Project gue aja bisa punya ratusan package di node_modules, dan mayoritasnya itu dependency dari dependency, yang gak pernah gue liat langsung. Yang gue butuhin cuma satu library parsing, tapi pas gue cek lockfile, di belakangnya ada dua puluh package lain yang ikut keseret. Setiap package itu punya maintainer, punya riwayat, dan punya celah potensial.
Kedua, base image. Image Docker yang gue pakai sebagai fondasi itu juga kode. Dulu gue pernah asal pake image dari registry publik yang gak jelas maintainer-nya, dan pas gue scan, ternyata di dalamnya ada versi OpenSSL yang udah punya CVE publik. Artinya gue deploy aplikasi yang kelihatan sehat, tapi fondasinya bolong.
Ketiga, pipeline dan runner CI. Ini yang sering banget kelewat. Kalau attacker bisa masuk ke runner yang ngejalanin build, mereka bisa ngubah kode, nyuntik malware, atau nyolong secret tanpa perlu nyentuh repository gue. Runner yang gak diamankan itu kayak kasir yang bisa diganti tengah malam tanpa ada yang curiga.
Keempat, registry dan distribution. Gimana image dan package gue didistribusikan, siapa yang bisa naruh artifact di situ, dan gimana gue mastiin yang gue tarik itu beneran yang gue publish. Kalau registry-nya gak dijaga, orang bisa naruh image palsu dengan nama yang mirip.
Kelima, manusia dan kebiasaan. Ini yang paling susah diubah. Kebiasaan nge-paste perintah install dari README tanpa baca, auto-merge PR dependabot tanpa cek, atau pin versi package selamanya tanpa pernah update. Semua itu pintu masuk yang gak butuh exploit canggih.
Yang Gue Ubah di Project, Satu per Satu
Pas gue udah paham peta masalahnya, gue mulai ubah kebiasaan dan infrastruktur gue pelan-pelan. Prinsipnya gue sederhana: jangan percaya sesuatu yang masuk ke produksi kalau gue gak bisa jelasin asalnya. Setiap lapisan yang gue tambahin di bawah ini pernah menyelamatin gue dari masalah, dan gue yakin bakal terus berguna.
Yang pertama gue beresin adalah kebiasaan main auto-merge. Sekarang setiap ada update dependency, gue wajib baca diff-nya. Kalau update-nya cuma naik versi patch, biasanya aman. Tapi kalau ada perubahan dependency tree, ada package baru yang masuk, atau ada perubahan di file yang gak nyambung sama release notes, gue berhenti dan selidiki. Gue juga matiin auto-merge buat PR dari bot, karena momen gue hampir kena itu justru dari PR yang ke-merge otomatis. Lockfile sekarang gue commit ke repository, dan gue perhatiin betul kalau ada package baru yang muncul tanpa alasan jelas.
Kedua, gue pasang scanner di pipeline. Bukan cuma sekali scan di akhir, tapi tiap ada perubahan dependency. Gue pakai yang open source dan gampang dipasang: OSV-Scanner buat ngecek vulnerabilities dari data OSV, dan Trivy buat scan image Docker lengkap dengan base image-nya. Scanner ini gak bikin gue aman otomatis, tapi mereka nangkep hal-hal yang gue gak mungkin cek manual: CVE di base image, package yang punya known vulnerability, sampai misconfiguration di Dockerfile. Ada satu momen di mana Trivy nangkep CVE kritis di base image yang udah gue pakai berminggu-minggu, dan gue langsung ganti ke versi yang lebih baru. Tanpa scanner, gue gak akan pernah tahu.
Ketiga, gue mulai serius soal SBOM. Singkatnya, SBOM itu daftar isi dari artifact gue: semua package, versi, dan lisensi yang ada di dalamnya. Dulu gue mikir ini cuma buang waktu, sampai suatu hari gue butuh jawab pertanyaan "apakah kita kena CVE ini?" dan gue harus manual nyari satu-satu. Dengan SBOM yang digenerate tiap build, jawabannya tinggal grep. Sekarang gue generate SBOM dalam format CycloneDX di setiap pipeline, simpan sebagai artifact, dan gue bisa liat riwayat komposisi setiap versi yang pernah gue rilis. Pas ada CVE baru muncul, gue tinggal cek SBOM-nya, bukan panik nebak-nebak.
Keempat, signing dan provenance. Ini lapisan yang agak teknis, tapi konsepnya gampang: gue tanda tangan image gue dengan kunci kriptografi, dan siapa pun yang narik image itu bisa verifikasi bahwa image itu beneran dari gue, bukan dari orang lain. Gue pakai cosign dari proyek Sigstore, dan sekarang di CI gue ada langkah buat nandatanganin image sebelum di-push ke registry. Di sisi deployment, gue pasang verifikasi: image yang gak punya tanda tangan yang valid gak akan pernah ditarik ke server. Ini nutup celah serangan kayak attacker yang naruh image palsu di registry dengan nama mirip.
Kelima, gue beresin hygiene registry dan base image. Sekarang gue cuma narik base image dari source yang jelas dan gue pin versinya, bukan tag yang bisa berubah-ubah kayak latest. Gue juga rutin rebuild image, karena image yang dibangun setahun lalu itu isinya outdated dan penuh CVE yang sebenernya udah ke-fix di versi baru. Kalau gue butuh package dari registry publik, gue narik lewat proxy yang ke-cache di registry internal, jadi gue punya catatan semua yang masuk dan bisa ngeblokir yang mencurigakan.
Keenam, runner CI. Ini yang paling gue remehin dulu. Gue sempet pake self-hosted runner di mesin yang sama dengan tempat gue nyimpen hal-hal sensitif. Salah banget. Sekarang runner yang ngejalanin build dari pull request gak punya akses ke secret produksi, dan build dari kontributor yang gak dikenal dijalanin di environment yang terisolasi. Kalau kode jahat jalan di runner, dia gak bisa nyolong apa-apa.
Hal yang Gak Pernah Gue Skip Lagi
Selain teknis, ada beberapa kebiasaan kecil yang dampaknya gede banget, dan gue rasa ini yang bikin perbedaan antara project yang cuma pamerin security di README sama project yang beneran dijagain.
Gue gak pernah lagi nge-paste command install dari blog atau README tanpa cek nama package-nya. Typo satu huruf di nama package itu cara paling gampang buat kena typosquatting. Sekarang gue selalu cek package itu ada di registry resmi, siapa maintainer-nya, kapan terakhir diupdate, dan berapa banyak yang download. Package yang baru muncul tiba-tiba, jarang diupdate, terus dapet ribuan download, itu red flag.
Gue juga berhenti ngejar versi terbaru tanpa alasan. Update dependency itu bukan lomba. Yang penting bukan versi paling baru, tapi versi yang paling gue pahami dan yang paling aman. Gue sekarang punya jadwal rutin buat update dependency, bukan update dadakan tiap ada notif. Di luar jadwal itu, gue cuma update kalau ada CVE yang beneran kena project gue atau ada fix yang gue butuhin. Ini bikin hidup lebih tenang dan riwayat perubahan lebih gampang dilacak.
Dan yang gak kalah penting: gue mulai berbagi pengetahuan ini ke tim. Security supply chain itu bukan kerjaan satu orang. Kalau cuma gue yang paham, terus ada developer lain yang auto-merge PR tanpa baca, semua lapisan pertahanan gue gak ada artinya. Sekarang setiap ada kejadian supply chain yang ramai di berita, gue bahas singkat di grup, dan gue buat dokumen singkat soal kebiasaan yang harus diikuti. Bukan buat menggurui, tapi biar semua orang punya alarm yang sama.
Urutan Prioritas Kalau Lo Mulai dari Nol
Kalau lo baca semua di atas dan ngerasa kewalahan, gue paham banget. Dulu gue juga ngerasa gitu. Tapi kalau lo mulai dari nol, lo gak perlu langsung pasang semuanya. Ini urutan yang gue saranin berdasarkan pengalaman, dari yang paling gampang dan paling berdampak.
Mulai dari kebiasaan: baca diff dependency, jangan auto-merge, dan jangan asal install package. Ini gratis, gak butuh tool apa pun, dan udah nutup serangan typosquatting yang hampir kena gue. Setelah itu baru pasang scanner di CI, karena ini yang nangkep CVE yang gak mungkin lo cek manual. Lanjut ke base image: pin versi, cuma pakai image dari source yang jelas, dan rutin rebuild. Pas udah stabil, baru deh beranjak ke SBOM dan signing. SBOM itu mulai berguna pas lo udah punya beberapa versi rilis dan mulai ditanya soal CVE, sementara signing itu paling kerasa manfaatnya pas lo udah deploy ke environment yang beneran produksi dan mau mastiin image yang jalan itu beneran punya lo.
Yang penting, jangan tunggu sempurna baru mulai. Mulai dari satu kebiasaan kecil hari ini, terus tambah lapisan berikutnya. Supply chain security itu kayak naik tangga, bukan lompat ke puncak.
Pelajaran Terbesar dari Semua Ini
Kalau gue rangkum semua yang gue pelajari dari malam Jumat yang bikin jantung gue copot itu, intinya cuma satu: kepercayaan itu harus dibangun, bukan diwariskan. Setiap dependency yang lo tarik, setiap base image yang lo pakai, setiap runner yang lo jalanin, itu semua pinjaman kepercayaan. Dan kepercayaan yang gak pernah dicek itu gampang disalahgunakan.
Sekarang gue gak paranoid, tapi gue waspada. Gue gak mikir semua package itu jahat, tapi gue juga gak mikir semua package itu baik. Gue cek, gue verifikasi, dan gue pastiin setiap lapisan punya jejak yang bisa dijelasin. Dan yang paling penting, gue udah gak pernah lagi ngerasain momen panik kayak malam itu, karena kalau ada yang aneh masuk ke pipeline, gue udah punya alat dan kebiasaan buat nangkepnya lebih awal.
Supply chain security itu bukan proyek yang selesai dalam seminggu, dan bukan juga sesuatu yang bisa lo beli dalam bentuk tool sekali pasang. Ini proses yang jalan terus, sejalan dengan cara tim lo nulis kode dan nge-deploy. Tapi kabar baiknya, lo gak perlu jadi expert dulu buat mulai. Mulai dari satu kebiasaan, satu lapisan, satu pipeline. Sisanya bakal ngikutin.