Ringkasan Utama
LiteLLM 1.82.7 dan 1.82.8 di PyPI membawa malware pencuri credential dan baru di-quarantine 2 jam 32 menit setelah upload, dengan lebih dari 119.000 download. Dependency cooldown seperti uv exclude-newer P3D menjaga resolusi baru tetap di luar jendela sesingkat itu. Hash lock, Trusted Publishers, dan audit workflow menutup celah yang berbeda.
Angka yang paling menempel di kepala saya dari incident report PyPI bukan 119.000 download. Angkanya 2j 32m: selisih waktu antara file LiteLLM berbahaya di-upload dan saat PyPI melakukan quarantine. Install apa pun yang berjalan di dalam jendela itu ikut terpapar. Install yang menunggu sehari tidak.
Post ini adalah pembacaan sumber primer soal kompromi LiteLLM bulan Maret 2026: incident report PyPI, security update dari LiteLLM sendiri, advisory PYSEC-2026-2, dan tulisan penemuan dari FutureSearch, ditambah dokumentasi uv, pip, dan PyPI di balik tiap kontrol. Saya tidak mereproduksi malware-nya dan ini bukan panduan exploit. Post ini untuk orang yang memegang file requirements atau CI runner dan ingin tahu setting mana yang akan berpengaruh. Kalau sumbernya saling berbeda, saya sebutkan.
Apa yang dipublikasikan, dan apa yang tidak pernah berisiko
Dua rilis litellm di PyPI berbahaya: 1.82.7 dan 1.82.8. Menurut pengumuman LiteLLM, 1.82.7 membawa payload di proxy_server.py milik proxy, dan 1.82.8 membawa payload yang sama ditambah file bernama litellm_init.pth. PYSEC-2026-2 mencatat rentang terdampak mulai 1.82.7 sampai 1.82.8. Pengumuman yang sama juga teliti soal apa yang tetap bersih, dan daftar itu adalah tempat mulai yang tepat bagi kebanyakan pembaca.
| Artifact | Status | Alasannya, menurut LiteLLM dan PYSEC-2026-2 |
|---|---|---|
| litellm 1.82.6 dan sebelumnya | Tidak terdampak | Dirilis sebelum insiden. Berlaku untuk pengguna yang tidak upgrade selama jendela itu. |
| litellm 1.82.7 | Berbahaya | Payload di proxy/proxy_server.py. |
| litellm 1.82.8 | Berbahaya | litellm_init.pth di site-packages, plus payload di proxy_server.py. |
| Image Docker proxy resmi (ghcr.io/berriai/litellm) | Tidak terdampak | Kata LiteLLM, image itu mem-pin dependency di requirements.txt dan tidak bergantung pada paket PyPI yang terkompromi. |
| Install dari repositori source di GitHub | Tidak terdampak | LiteLLM menyatakan repositori tidak terkompromi dan tidak ada kode berbahaya yang di-push ke main. |
| LiteLLM Cloud | Tidak terdampak | Dinyatakan oleh LiteLLM dalam security update-nya. |
Polanya di tabel itulah intinya. Semua yang me-resolve dependency baru dari PyPI selama jendela itu terpapar. Semua yang install dari daftar yang di-pin, image bertanda tangan, atau source checkout tidak. LiteLLM menambahkan satu catatan yang mudah terlewat: image Docker yang di-build selama jendela itu dengan pip install litellm tanpa pin, dan proyek yang membawa LiteLLM sebagai transitive dependency tanpa pin, dihitung terdampak. FutureSearch menemukannya persis dengan cara itu, sebagai transitive dependency dari plugin MCP yang berjalan di dalam Cursor.
Kenapa file .pth hook yang lebih baik daripada fungsi jahat
Fungsi jahat hanya jalan kalau ada yang memanggilnya. File .pth jalan saat Python start. Dokumentasi Python untuk modul site menyebut baris di file .pth yang diawali import dieksekusi dengan exec(), dan baris executable itu selalu jalan saat interpreter start kecuali -S menonaktifkan site.py, terlepas dari apakah modulnya akan dipakai. FutureSearch menyebut litellm_init.pth dieksekusi di setiap start proses Python setelah paketnya terpasang.
Itu sebabnya blast radius-nya lebih besar daripada perkiraan orang yang menjalankan LiteLLM. Memasang paket di virtualenv berarti setiap proses Python yang memakai environment itu, termasuk pip, linter, dan test runner, menjalankan hook-nya. FutureSearch juga melaporkan launcher-nya memunculkan child process Python yang memicu ulang file yang sama, sehingga terjadi fork bomb eksponensial yang membuat mesin crash. Penulisnya menyebut itu cacat di malware-nya, bukan fitur, dan mesin yang crash sulit terlewat. Malware yang lebih senyap tidak akan menarik perhatian sebesar itu.
# The rule from the Python docs (site module): lines in a .pth file that
# start with "import" are exec()'d, and they run at every interpreter
# start unless python -S is used, whether or not the module is ever imported.
#
# Audit: list every .pth line in this environment that would execute code.
# Expect a few legitimate hits (editable installs, some build tools), so
# diff the output against a known-good baseline instead of panicking.
python3 -I - <<'EOF'
import pathlib
import site
dirs = site.getsitepackages() + [site.getusersitepackages()]
for d in dirs:
for pth in sorted(pathlib.Path(d).glob("*.pth")):
text = pth.read_text(errors="replace")
for n, line in enumerate(text.splitlines(), 1):
if line.startswith(("import ", "import\t")):
print(pth, n, line[:120])
EOFPython 3.15 men-deprecate baris import di file .pth dan rilis berikutnya akan mengabaikannya, menurut dokumentasi yang sama, tapi itu masih jauh dari interpreter production Anda hari ini. Sampai saat itu, mengaudit baris tersebut adalah kontrol yang murah, dan script di atas melakukannya hanya dengan standard library.
Bagaimana publish terjadi, dan apa yang belum terbukti
Incident report PyPI menyebut penyebabnya adalah API token yang terekspos lewat dependency Trivy yang dieksploitasi. PYSEC-2026-2 mengulang kerangka itu: setelah sebuah token terekspos lewat dependency Trivy yang dieksploitasi, dua rilis litellm baru di-upload. Laporan itu tidak menjelaskan bagaimana token tersebut berujung pada hak publish, dan saya tidak akan menebak-nebak.
Pengumuman LiteLLM sendiri lebih berhati-hati. Disebutkan bukti saat ini menunjukkan akun PyPI seorang maintainer mungkin terkompromi, dan juga bahwa tim meyakini kompromi berawal dari dependency Trivy yang dipakai di workflow CI/CD security scanning mereka. Ditambahkan pula bahwa bukti awal menunjukkan penyerang melewati workflow CI/CD resmi dan meng-upload paket langsung ke PyPI. Pengamatan FutureSearch cocok dengan itu: mereka tidak menemukan tag atau release yang cocok di repositori GitHub litellm untuk versi berbahaya tersebut.
Sumber-sumbernya berbeda soal waktu. LiteLLM menyebut rilis itu live sekitar 40 menit mulai pukul 10:39 UTC tanggal 24 Maret. PyPI mengukur 2j 32m dari upload sampai quarantine. FutureSearch mencatat 1.82.8 dipublikasikan pukul 10:52 UTC. Di post ini saya memakai durasi dari PyPI karena PyPI yang menjalankan quarantine, tapi anggap setiap timestamp tunggal sebagai perkiraan.
Perubahan yang dilakukan LiteLLM sesudahnya adalah bagian yang berguna bagi siapa pun yang menjalankan release pipeline. Mereka menghentikan rilis, merotasi credential maintainer, melibatkan Mandiant milik Google untuk forensik, dan mengaudit artifact PyPI dan Docker dari v1.78.0 sampai v1.82.6 dengan memeriksa indikator, membandingkannya dengan Git commit, dan mencatat digest SHA-256. Mereka merilis v1.83.0 lewat pipeline CI/CD v2 yang baru dan mulai menandatangani image Docker dengan cosign.
Aritmetika paparan
PyPI mempublikasikan pengukurannya sendiri untuk kedua paket yang kena di kampanye ini, litellm dan telnyx. Keduanya layak dibaca berpasangan, karena menunjukkan deteksi berjalan namun tetap butuh berjam-jam.
| Metrik | LiteLLM | Telnyx |
|---|---|---|
| Upload sampai laporan pertama | 1j 19m | 1j 45m |
| Laporan pertama sampai quarantine | 1j 12m | 1j 57m |
| Upload sampai quarantine (total paparan) | 2j 32m | 3j 42m |
| Download dan laporan | Lebih dari 119 ribu download versi terdampak; 13 laporan malware masuk | Jumlah download tidak disebut; respons otomatis dari laporan berbobot oleh trusted reporter |
Dua angka menjelaskan kenapa 119.000 download itu terjadi. PyPI menyebut LiteLLM di-install sekitar 15 sampai 20 juta kali seminggu, yang ia bulatkan menjadi sekitar 1.700 install per menit, dan memperkirakan 40 sampai 50 persen dari semua install tidak di-pin dan mengambil versi terbaru. Jadi kira-kira separuh orang yang meng-install secara efektif menawarkan diri menjalankan apa pun yang paling baru.
Cooldown tiga hari berarti 72 jam. Dibanding jendela 2j 32m, itu sekitar 28 kali lebih panjang dari paparannya, dan dibanding 3j 42m milik Telnyx sekitar 19 kali. Hitungan itu dari saya, bukan dari PyPI, dan mengasumsikan kecepatan deteksi yang sama di kejadian berikutnya. Itulah alasan cooldown menjadi kontrol yang didahulukan PyPI.
Empat kontrol dibandingkan, dan kelima yang sudah dimiliki LiteLLM
Laporan PyPI merekomendasikan beberapa kontrol dan tidak mengurutkannya. Tabel di bawah adalah penilaian saya sendiri terhadap tiap kontrol untuk insiden ini secara spesifik. Baca kolom terakhir dua kali: setiap kontrol punya kasus yang tidak ia tutup.
| Kontrol | Apa yang dihentikan | Untuk insiden ini | Titik buta |
|---|---|---|---|
| Dependency cooldown (uv exclude-newer, pip uploaded-prior-to) | Me-resolve rilis yang di-upload dalam N hari terakhir | Ya untuk resolusi baru mana pun: jendelanya hitungan jam, cooldown P3D adalah 72 jam | Bergantung pada orang lain yang mendeteksi rilis lebih dulu; menunda perbaikan yang sah |
| Lock file dengan hash pin | File berbeda yang datang dengan versi yang sudah Anda lock | Hanya kalau Anda lock sebelum 1.82.7 ada | Hash membuktikan identitas, bukan keamanan: lock sesudah upload akan mem-pin file berbahaya |
| Trusted Publishers | Pencurian token upload PyPI yang berumur panjang | Menghilangkan jenis token itu; kata PyPI token hasil pertukaran kedaluwarsa setelah 15 menit | Kontrol di sisi publisher: konsumen tidak mendapat apa-apa darinya, dan tidak menolong kalau penyerang bisa menjalankan workflow yang dipercaya |
| Audit workflow (zizmor, SHA pin, approval environment) | Salah konfigurasi CI yang membocorkan secret ke penyerang | Menarget akar masalah yang disebut, token yang terekspos lewat CI | Analisis statis file workflow; tidak bisa memberi tahu bahwa dependency yang Anda pin ternyata sendiri terkompromi |
| Requirements yang di-pin atau image resmi yang di-pin | Apa pun yang di-resolve saat build | LiteLLM menyebut image resmi tidak terdampak karena mem-pin | Anda sekarang memegang proses bump, dan pin yang basi adalah risiko tersendiri |
Dibaca sebagai satu set, kontrol-kontrol ini terbagi menurut siapa yang memegangnya. Cooldown, hash, dan pin melindungi konsumen. Trusted Publishers dan audit workflow melindungi maintainer. Insiden seperti ini butuh kedua sisi, tapi hanya sisi konsumen yang ada di tangan Anda minggu ini.
Config yang bisa langsung ditempel
Setiap setting di bawah berasal dari dokumentasi uv, dokumentasi pip, atau incident report PyPI. Cutoff uv menerima timestamp RFC 3339, tanggal lokal, dan durasi seperti 1 week atau bentuk ISO P7D. Bulan dan tahun kalender ditolak, dan satu hari dihitung 24 jam. Opsi pip didokumentasikan menerima datetime ISO 8601 atau durasi dalam hari, dan hanya efektif pada index yang menyediakan metadata upload-time.
# --- uv: ~/.config/uv/uv.toml (user-wide) or pyproject.toml (per project) ---
# Cooldown: ignore any file uploaded in the last 3 days. The cutoff compares
# each FILE's upload time, and the resolved timestamp is stored in uv.lock.
# pyproject.toml form:
[tool.uv]
exclude-newer = "P3D"
# Need one brand-new release on purpose (say, a fix you have read the diff of)?
# Opt that package out instead of lowering the global window.
[tool.uv]
exclude-newer = "P3D"
exclude-newer-package = { somepackage = false }
# --- pip: ~/.config/pip/pip.conf ---
[install]
uploaded-prior-to = P3D
# pip on the command line, absolute cutoff (GNU date shown):
python -m pip install --uploaded-prior-to=$(date -d '-3days' -Idate) somepackage
# --- hash-pinned requirements: identity, not safety ---
pip-compile --generate-hashes requirements.in -o requirements.txt
python -m pip install --require-hashes -r requirements.txt
# What requirements.txt then looks like (every dependency pinned and hashed):
# somepackage==1.2.3 \
# --hash=sha256:<64 hex chars from the index> \
# --hash=sha256:<one per wheel or sdist you may install>Di sisi publishing, panduan PyPA memisahkan build dari publish, mewajibkan id-token: write pada job publish, dan menyatakan Anda harus mewajibkan manual approval di setiap run untuk environment pypi. Panduan itu juga menyebut membangun distribution di dalam job publishing tidak didukung. Incident report PyPI secara terpisah menyarankan mem-pin action ke commit SHA, meneruskan input lewat environment variable, dan menghindari trigger yang bisa dipengaruhi penyerang, seperti pull_request_target.
# Publish job shaped like the PyPA guide: separate from build, no stored
# token, and a protected environment that needs a human click on each run.
publish-to-pypi:
if: startsWith(github.ref, 'refs/tags/')
needs:
- build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/your-package-name
permissions:
id-token: write # mandatory: lets the job mint an OIDC token
steps:
- uses: actions/download-artifact@v6
with:
name: python-package-distributions
path: dist/
# PyPA's guide uses the moving ref release/v1. PyPI's own incident
# guidance is to pin third-party actions to a full commit SHA instead.
- uses: pypa/gh-action-pypi-publish@release/v1
# Audit the workflows you already have. Exit 11-14 = findings by severity.
zizmor .github/workflows/
zizmor --format=sarif .github/workflows/ > zizmor.sarifSatu detail versi perlu dicek sebelum Anda mengandalkan file config pip. Laporan PyPI yang ditulis bulan April menyebut pip 26.0 ke atas menerima tanggal absolut di command line dan bentuk config file butuh 26.1, yang saat itu disebut akan rilis bulan itu. Jalankan pip install --help di versi Anda sendiri dan cari uploaded-prior-to sebelum berasumsi opsinya ada.
Taruh cooldown di file level user, bukan di tiap repositori. Proyek yang lupa menyetelnya tetap mewarisinya, dan repo yang tidak pernah Anda sentuh mendapat perlindungan yang sama dengan yang Anda rawat.
Biaya cooldown, dan di mana ia gagal
Cooldown adalah taruhan bahwa orang lain melihat lebih dulu. Ia tidak berbuat apa-apa terhadap paket yang berbahaya selama seminggu sebelum ada yang sadar, dan ia berhasil di sini hanya karena deteksi terjadi dalam hitungan jam. Ia juga menunda rilis yang jujur: bug fix yang Anda butuhkan hari ini tidak akan ter-resolve sampai cutoff lewat, kecuali Anda mengecualikan paket itu. Di uv pengecualiannya adalah exclude-newer-package yang disetel false untuk nama itu, yang menimpa nilai global.
Security update adalah sisi yang tajam. PyPI mencatat bahwa Dependabot dan Renovate melewati cooldown untuk security update secara default, yang masuk akal untuk perbaikan CVE yang nyata. Itu juga berarti rilis yang disajikan sebagai security fix adalah jenis yang akan diloloskan cooldown secara desain, jadi manusia tetap harus membaca diff dari update yang di-bypass. Poin kedua itu adalah kesimpulan saya, bukan klaim dari laporan.
Dokumentasi uv mencantumkan tiga celah. Index harus menyediakan data upload-time (PEP 700), kalau tidak uv menganggap distribution itu tidak tersedia. Cutoff hanya mencakup paket registry, bukan dependency Git. Dan pesan error untuk resolusi yang tidak terpenuhi tidak menyebut bahwa cutoff mengecualikan sesuatu, sehingga requirement transitive baru yang gagal ter-resolve bisa terlihat seperti bug di tempat lain. Ingat hal itu sebelum membuka issue.
Hash lock tidak mendeteksi malware. Kalau Anda menjalankan pip-compile --generate-hashes sehari setelah rilis buruk, Anda dengan setia mem-pin file buruk itu dengan hash yang valid. Hash melindungi lock yang Anda buat saat dunia masih bersih, dan cooldown-lah yang memastikan itu benar.
Yang dikerjakan hari Senin
Berurutan, dari yang termurah. Langkah satu dan dua memberi tahu apakah Anda terpapar. Langkah tiga sampai enam membuat insiden berikutnya membosankan.
- Cari di setiap environment yang Anda miliki, termasuk CI runner, build host, dan package cache, untuk litellm 1.82.7 dan 1.82.8, serta untuk litellm_init.pth, memakai perintah di bawah.
- Kalau menemukan salah satunya, ikuti remediation LiteLLM: hapus, selidiki host untuk kompromi lanjutan, dan rotasi setiap secret yang bisa dijangkau host itu, termasuk API key, cloud key, SSH key, token Kubernetes, dan password database. Bersihkan cache uv dan pip supaya wheel buruk itu tidak kembali.
- Setel cooldown level user hari ini: exclude-newer P3D di uv.toml, atau uploaded-prior-to di pip.conf begitu pip Anda mendukungnya. Pilih jendelanya dengan sengaja dan catat siapa yang boleh mem-bypass.
- Untuk apa pun yang di-deploy, install dari lock dengan hash pin memakai --require-hashes, sehingga wheel yang berubah di bawah nomor versi gagal dengan jelas, bukan ter-install diam-diam.
- Kalau Anda mempublikasikan paket, pindah ke Trusted Publishers, pisahkan build dari publish, taruh environment pypi di belakang manual approval, dan jalankan zizmor pada .github/workflows di CI.
- Tambahkan dua domain yang disebut di write-up, models.litellm[.]cloud dan checkmarx[.]zone, ke alerting egress Anda, dan tinjau berapa banyak image Anda yang di-build dengan pip install tanpa pin.
# Detection only. Run on dev machines, CI runners and build hosts.
# Indicators come from LiteLLM's notice, PyPI advisory PYSEC-2026-2 and
# FutureSearch's write-up; none of these commands touch the network.
# 1. Which version is installed, and was it ever (1.82.7 or 1.82.8)?
python -m pip show litellm | grep -i '^version'
# 2. The .pth file both write-ups name. Check venvs AND package caches.
find / -name "litellm_init.pth" -not -path "/proc/*" 2>/dev/null
find ~/.cache/uv -name "litellm_init.pth" 2>/dev/null
# 3. Persistence paths FutureSearch lists, and the systemd unit name
# PYSEC-2026-2 says was disguised as "System Telemetry Service".
ls -l ~/.config/sysmon/sysmon.py ~/.config/systemd/user/sysmon.service 2>/dev/null
systemctl --user list-units --all | grep -i telemetry
# 4. Kubernetes: FutureSearch says to audit kube-system for node-setup-* pods.
kubectl -n kube-system get pods | grep '^node-setup-'
# 5. Egress: search DNS, proxy and firewall logs (not this host) for the two
# domains the write-ups name: models.litellm[.]cloud and checkmarx[.]zone.
# Neither belongs to LiteLLM.Tidak satu pun perintah ini mengirim data ke mana-mana, dan semuanya hanya mencari indikator yang dipublikasikan sumber yang dikutip. Kalau sumber kelak merevisi daftar indikatornya, seperti LiteLLM yang menambahkan domain kedua pada 26 Maret, perbarui script-nya, jangan hanya percaya snapshot ini.
Aturan yang saya bawa dari insiden ini: meng-install rilis terbaru begitu ia ada adalah sebuah keputusan, dan seharusnya keputusan yang Anda ambil dengan sengaja. Cooldown mengubahnya menjadi default tiga hari, hash lock mengubah setiap install berikutnya menjadi perbandingan, dan image atau lock file yang di-pin mengubah upgrade tanpa review menjadi pull request. Tidak satu pun dari ini akan menghentikan penyerangnya. Semuanya akan menjaga sebagian besar dari 119.000 install itu di luar jendelanya.
Sumber dan bacaan lanjutan
- PyPI blog: incident report serangan supply chain LiteLLM dan Telnyx (2 April 2026)
- LiteLLM: security update, Maret 2026
- PyPA advisory database: PYSEC-2026-2 untuk litellm
- FutureSearch: bagaimana kompromi PyPI litellm ditemukan
- Dokumentasi uv: resolution, exclude-newer, dan dependency cooldown
- Dokumentasi pip: opsi pip install, termasuk uploaded-prior-to
- Dokumentasi pip: secure installs dan hash-checking mode
- Dokumentasi PyPI: Trusted Publishers
- Python Packaging User Guide: publishing dengan GitHub Actions dan trusted publishing
- Dokumentasi zizmor: usage, format output, dan exit code
- Dokumentasi Python: modul site dan file .pth

