Serangan Supply Chain PyPI: Pelajaran dari LiteLLM
Kompromi paket LiteLLM di PyPI menyisipkan pencuri kredensial ke setiap wheel. Ini yang bisa dan tidak bisa dicegah oleh hash pinning dan lockfile.

Tiga file. Itulah keseluruhan jejak salah satu serangan supply chain Python paling parah yang pernah kami lihat: cook.pth berukuran 12 byte, cook.py berukuran 22 KB, dan sitecustomize.py berukuran 25 KB. Saat LiteLLM 1.107.0 dirilis di PyPI, ketiga file itu ikut terbawa di dalam ketujuh wheel-nya. Karena cara kerja startup Python, file-file tersebut dieksekusi di setiap proses Python pada host yang terinfeksi: aplikasi kamu, runner CI, pip itu sendiri, bahkan language server di editor kamu. Kamu tidak perlu meng-import litellm. Kamu tidak perlu menjalankannya. Cukup menjalankan interpreter Python apa pun sudah cukup.
Tim LiteLLM mengonfirmasi kompromi tersebut dan PyPI menarik rilis itu dalam beberapa jam, tetapi jendela waktunya cukup panjang. Image Docker yang berbahaya, termasuk tag latest, aktif selama kurang lebih dua setengah jam. Kalau CI kamu melakukan pull dalam rentang waktu itu, stealer sudah sempat mengambil SSH key, kredensial cloud, password browser, secret CI/CD, dan token tool AI, lalu mengenkripsinya dan mengirimkannya ke domain milik penyerang. Ini bukan typosquatting atau paket yang namanya mirip. Ini paket asli, dibangun oleh pipeline asli, dan ditandatangani oleh proses rilis yang asli. Hal ini seharusnya mengubah cara kamu memandang kepercayaan terhadap dependensi.
Bagaimana Tiga File Membajak Pipeline Rilis Resmi
Penyerang tidak meretas PyPI, dan mereka juga tidak menyelundupkan commit ke repositori publik. Source di GitHub upstream tetap bersih. Menurut laporan insiden LiteLLM, penyerang memegang kredensial GitHub CLI milik tiga developer, yang tampaknya diambil dari log deployment internal LiteLLM yang terekspos ke internet. Dengan token milik owner organisasi, mereka melewati branch protection dan mengarahkan workflow rilis privat ke repositori yang namanya mirip, Berri-AI/litellm alih-alih BerriAI/litellm. Kredensial developer kedua kemudian memenuhi gerbang persetujuan. Pipeline resmi pun membangun, menandatangani, dan mempublikasikan artefak milik penyerang.
Injeksinya sendiri sangat ringkas. Hanya satu baris tambahan di pyproject.toml milik sdist:
[tool.maturin]
python-source = "."
data = "cook" # <-- injected, copies cook/ into the wheel
python-packages = ["litellm_cli"] # legitimate, present upstream
Directive data = "cook" itu memberi tahu maturin untuk memasukkan direktori cook/ ke path purelib di dalam wheel, lalu pip dengan patuh menginstalnya ke site-packages. sdist-nya sendiri tidak menyertakan folder tersebut, jadi siapa pun yang sekilas memeriksa rilisnya hanya akan melihat satu baris membingungkan dan tidak ada yang lain. Payload-nya bersembunyi di artefak build, bukan di source. Menelusuri riwayat git, hal yang kebanyakan dari kita anggap sebagai kebenaran mutlak, tidak akan menunjukkan apa pun.
Mengapa File .pth dan sitecustomize.py Adalah Surga bagi Malware
Dua fitur dalam urutan startup Python membuat serangan ini sangat berbahaya, dan jujur saja, kebanyakan developer Python belum pernah memikirkannya. Pertama: setiap file .pth di site-packages diproses saat interpreter dijalankan, dan baris yang diawali import akan dieksekusi. File cook.pth berukuran 12 byte itu hanya berisi satu baris, yaitu import cook, dan itu sudah cukup untuk menjalankan stealer di setiap proses Python di mesin tersebut. Kedua: Python otomatis meng-import modul bernama sitecustomize saat startup jika menemukannya di path. Penyerang menaruh file milik mereka sendiri, yang lalu membuat daemon terpisah. Daemon ini menghubungi file teks yang di-host di GitHub setiap jam, mengunduh kode apa pun yang ditunjuk penyerang, dan menjalankannya dengan exec().
Ini jauh lebih berbahaya daripada paket jahat biasa yang menyisipkan kode di setup.py atau impor bermasalah di __init__.py. Kode-kode itu berjalan saat kamu menginstal atau meng-import paketnya. Yang ini berjalan setiap kali kamu menjalankan apa pun di Python. Perintah pytest di CI kamu. Perintah pip list yang kamu ketik untuk memeriksa apa yang terjadi. Formatter black di IDE kamu. Setiap pemanggilan mengaktifkan jebakannya lagi.
Ada baiknya kita berhenti sejenak dan melihat apa saja yang sebenarnya dikumpulkan oleh stealer ini, karena daftarnya seperti peta kehidupan developer modern: kredensial AWS, Azure, dan gcloud, kubeconfig, token GitHub CLI, key SSH dan GPG, penyimpanan password browser, riwayat shell, secret CI dari GitLab runner dan Jenkins, kredensial Terraform, environment variable milik proses lain yang dibaca lewat /proc/<pid>/environ, kredensial dari instance metadata AWS, dan, dengan ironi yang getir, token untuk Claude Code, Cursor, Copilot, serta selusin tool coding AI lainnya, ditambah key provider LLM yang tersimpan di database proxy milik LiteLLM sendiri. Cukup dengan satu perintah pip install, penyerang bisa memegang hampir seluruh identitasmu sebagai engineer.
Kamu tidak perlu meng-import litellm. Cukup menjalankan interpreter Python apa pun di host itu sudah cukup untuk menjalankan stealer.
Hash Pinning dan Lockfile: Perlu, Tapi Tidak Cukup
Setiap checklist keamanan supply chain dimulai dengan lockfile dan hash pinning, dan memang seharusnya begitu. Jika kamu mengunci hash dengan pip install --require-hashes atau memakai tool seperti uv atau Poetry dengan hash yang terkunci, wheel yang diam-diam berubah setelah kamu verifikasi akan ditolak. Itu perlindungan nyata terhadap banyak jenis serangan, seperti manipulasi registry, mirror yang diracuni, dan penggantian artefak.
Tapi mari jujur soal apa yang dilakukannya di kasus ini: hampir tidak ada, setidaknya untuk siapa pun yang upgrade lebih dulu. Hash pinning menjamin bahwa artefak yang kamu instal besok identik byte per byte dengan yang kamu instal hari ini. Dalam serangan ini, wheel berbahaya itu memang artefak resmi: milik pihak pertama, dibangun oleh pipeline, dan hash-nya konsisten. Begitu seseorang menjalankan upgrade rutin, me-resolve versi baru, dan meng-commit lockfile yang sudah diperbarui, hash berbahaya itu tercatat di repo dengan centang hijau dari CI. Pinning melindungimu dari perubahan, tapi tidak mengatakan apa pun tentang niat.
Di cerita seperti ini, pinning memang bersinar dalam hal radius dampak. Tim yang memakai hash terkunci dan jadwal upgrade yang disengaja tidak otomatis ikut setiap rilis begitu rilis itu keluar. Jika kebijakan kamu adalah "upgrade dependensi lewat PR mingguan, dengan masa tunggu beberapa hari untuk apa pun yang bukan perbaikan kritis", maka penarikan paket dari PyPI pada pukul 13.23 UTC sudah terjadi sebelum jendela upgrade kamu dibuka. Proses yang membosankan sering kali lebih ampuh daripada tooling yang canggih, meskipun kita sering enggan mengakuinya.
Jadi, tetaplah pakai lockfile. Generate dengan hash. Tapi berhentilah membohongi diri sendiri bahwa itu pertahanan terhadap pipeline maintainer yang sudah disusupi. Lockfile melindungi dari drift dan manipulasi, serta berfungsi sebagai polisi tidur untuk rilis yang buruk. Itu saja.

Provenance Attestation Akan Mendeteksi Serangan Ini
Ini sisi lain yang tidak nyaman: serangan khusus ini punya anomali yang mencolok dan bisa dideteksi, dan tooling ekosistem untuk mendeteksinya sudah tersedia sekarang. Wheel berbahaya itu tidak dibangun dari repositori publik. Mereka dibangun dari repositori mirip milik penyerang. Langkah verifikasi apa pun yang menanyakan "apakah artefak ini dibangun dari commit dan repositori yang diklaimnya?" pasti akan gagal dengan sangat jelas.
Itulah yang diberikan oleh provenance attestation. PyPI mendukung attestation PEP 740: saat paket dipublikasikan lewat Trusted Publishers dari GitHub Actions, workflow dapat menghasilkan pernyataan bertanda tangan yang mengikat artefak ke repositori, workflow, dan commit yang tepat. Pemeriksaan di sisi konsumen menjadi mekanis: verifikasi attestation, pastikan repo sumbernya adalah BerriAI/litellm, bukan Berri-AI/litellm. Bundle Sigstore, provenance SLSA, dan verifikasi gaya pip-audit semuanya berada dalam keluarga ini.
Kesenjangan saat ini ada pada adopsi dan penegakan. Attestation bersifat opt-in bagi publisher, dan hampir tidak ada yang memverifikasinya saat instalasi, bahkan ketika attestation itu ada. Di sinilah perubahan perlu dilakukan, dan ini sejalan dengan poin yang pernah kami bahas tentang kepercayaan pada infrastruktur trust hanya berfungsi jika ada yang benar-benar memeriksanya: tanda tangan yang tidak pernah diverifikasi hanyalah hiasan. Jika organisasi kamu menjalankan package index atau proxy sendiri, dan jika kamu punya lebih dari segelintir engineer, kamu seharusnya melakukannya, maka "tolak artefak tanpa provenance yang bisa diverifikasi dari sumber yang diharapkan" adalah kebijakan yang bisa kamu terapkan secara terpusat, sekali, untuk setiap developer.
Ada juga pelajaran yang lebih halus. Penyerang membutuhkan tiga kredensial developer: satu untuk mengalihkan workflow, satu untuk menyetujui, dan satu lagi untuk berjaga-jaga. Ini menunjukkan kegagalan sistemik dalam kebersihan kredensial, bukan satu kesalahan tunggal. Trusted Publisher berbasis OIDC di PyPI dengan token berumur pendek akan sangat mengurangi permukaan serangan, karena tidak ada token berumur panjang yang tersimpan di file log untuk dicuri. Jika project kamu masih mempublikasikan paket dengan API token yang disimpan sebagai secret CI, anggap ini sebagai pengingat.
Instalasi Terisolasi dan Alasan Perlunya Jeda Pendinginan
Anggap saja skenario terburuk: rilis berbahaya lolos dari kebersihan lockfile dan pemeriksaan provenance kamu, karena suatu saat hal itu pasti terjadi. Lalu apa yang tersisa? Containment. Pertanyaannya bukan lagi "bagaimana kita mencegah paket buruk", melainkan "ketika paket buruk berjalan, apa saja yang bisa dijangkaunya?"
Untuk CI, jawabannya memang bisa dicapai. Build sebaiknya berjalan di lingkungan sementara tanpa kredensial berumur panjang, dengan token yang dibatasi scope-nya dan egress filtering. Perilaku stealer LiteLLM cukup mencolok di jaringan jika kamu memperhatikannya: POST ke domain yang tidak dikenal dengan custom auth header, fetch dari URL raw GitHub setiap jam dalam loop, dan, tanda terbesar dari semuanya, proses Python yang melakukan query ke 169.254.169.254, yaitu instance metadata service AWS. Kode aplikasi hampir tidak pernah punya alasan sah untuk mengakses IMDS secara langsung, karena SDK sudah menanganinya. Pasang alert untuk proses Python yang menyentuh endpoint itu, dan kamu akan menangkap seluruh keluarga pencuri kredensial, bukan hanya yang ini. Kami pernah membahas titik buta dalam logging autentikasi cloud. Ini salah satunya, dan menutupnya cukup murah.
Mesin developer lebih sulit, dan saya tidak akan berpura-pura sebaliknya. Laptop tempat user yang sama menjalankan editor, browser, SSH agent, dan pip install sembarangan adalah surga bagi stealer. Langkah-langkah yang pragmatis: lakukan instalasi eksploratif di container atau devcontainer, bukan di environment host; pasang paket di dalam devcontainer project agar sitecustomize.py berbahaya tidak pernah masuk ke site-packages host; dan anggap ~/.aws/credentials sebagai liabilitas. SSO dengan token berumur pendek (aws sso login, gcloud auth) berarti jauh lebih sedikit data tersimpan di disk yang bisa dicuri. Tidak ada yang eksotis di sini. Ini hanya soal friksi, dan friksi itulah intinya.
- Pin versi dan hash di lockfile, tetapi perlakukan upgrade sebagai keputusan yang disengaja, bukan kebisingan latar belakang.
- Terapkan jendela pendinginan selama 48 sampai 72 jam sebelum upgrade dependensi non-kritis masuk ke build production. Sebagian besar rilis berbahaya ditarik dalam hitungan jam.
- Verifikasi provenance attestation jika tersedia, dan utamakan paket yang dipublikasikan lewat Trusted Publishers dengan OIDC dibanding API token berumur panjang.
- Jalankan build CI di lingkungan sementara tanpa kredensial, dengan egress filtering, dan pasang alert untuk setiap proses Python yang menyentuh endpoint instance metadata.
- Instal dan jalankan kode pihak ketiga di container pada mesin developer, dan ganti file kredensial cloud statis dengan sesi SSO berumur pendek.
- Periksa environment kamu dari IoC jika kamu menarik paket apa pun dalam jendela waktu tersebut:
cook.pth,cook.py, atausitecustomize.pyyang tidak semestinya di site-packages, serta/tmp/.pc_once_*atau/tmp/remote_code_loader.lockdi disk.
Apa yang Dikatakan Serangan Ini tentang Letak Risiko Sebenarnya
Perhatikan apa yang tidak dilakukan penyerang. Mereka tidak mengeksploitasi kerentanan kode. Mereka tidak butuh zero-day. Mereka mengincar target paling lunak dalam perangkat lunak modern: manusia dan mesin yang memegang kredensial aktif. Tiga token GitHub CLI, yang tampaknya diambil dari log deployment internal yang terekspos ke internet, sudah cukup untuk mengambil alih pipeline rilis yang melayani jutaan unduhan. Ini pola yang terus berulang di industri: secret yang berserakan di log, agent, dan file konfigurasi, masing-masing berfungsi sebagai kunci master. Jika kamu menjalankan instance internal dari apa pun dan mengeksposnya ke internet, log-nya sekarang menjadi bagian dari threat model kamu. Kalimat ini seharusnya membuat sebagian orang merasa tidak nyaman.
Ada juga masalah kecepatan yang layak disebutkan. LiteLLM adalah project yang bergerak cepat, dengan puluhan ribu commit dan rilis yang sering, dan ia berada di pusat stack tooling AI, ditarik oleh framework dan SDK yang banyak tim bahkan tidak tahu bergantung padanya. Jangkauan transitif berarti kamu tidak bisa memilih eksposur hanya dengan memilih dependensi langsung. Di dunia seperti ini, SBOM bukan lagi sekadar dokumen kepatuhan; ia adalah cara kamu menjawab "apakah kita terdampak?" dalam hitungan menit, bukan hari. Tool yang membuat bill of materials dari bukti build yang sebenarnya, bukan dari scraping manifest, akan menemukan dependensi yang tidak disebut di requirements.txt kamu.

Pendapat saya: respons industri terhadap serangan supply chain terlalu berat di sisi pencegahan semu, seperti scanner yang menandai versi yang sudah diketahui buruk setelah kejadian, dan terlalu ringan di kontrol-kontrol membosankan yang sebenarnya bisa mengubah cerita ini. Kredensial publikasi berumur pendek. Verifikasi provenance di proxy. Jendela pendinginan. Alert egress ke endpoint metadata. Instalasi terisolasi. Tidak ada satu pun dari ini yang merupakan masalah riset. Ini keputusan konfigurasi yang tersedia hari ini, dan kebanyakan tim belum menerapkan satu pun. Penyerang LiteLLM hanya butuh satu baris buruk di konfigurasi build dan tiga token curian. Lapisan-lapisan pertahanan di atas akan memaksa mereka menembus empat lapisan independen, bukan satu. Itulah arti defense in depth yang sebenarnya: bukan lebih banyak scanner, melainkan lebih banyak cara independen agar serangan gagal. Ini filosofi yang sama dengan sistem rekayasa yang tahan terhadap bencana, bukan sistem yang sekadar berharap yang terbaik.
Rilis berbahaya berikutnya sudah sedang direncanakan oleh seseorang. Kamu tidak bisa mencegahnya untuk dipublikasikan. Tapi kamu sepenuhnya bisa memutuskan apakah ia berjalan dengan kunci AWS kamu dalam jangkauan, SSH agent yang terbuka, dan jalur jaringan yang jelas ke domain milik penyerang, atau apakah ia mati di container sementara tanpa kredensial dan di balik firewall. Keputusan itu dibuat sekarang, di konfigurasi CI dan setup devcontainer kamu, bukan pada hari laporan insiden dirilis.


