L424 Okt 2026EN · ID
Kode Asing di Samping Secret Production
Platform keamanan seharusnya tidak menjalankan kode yang sedang diperiksanya. Audit yang saya minta menunjukkan bahwa platform saya bisa melakukannya. Memproses sebuah repository bisa menjalankan kode milik repository itu di dalam API yang terbuka ke internet, di environment yang memegang secret production, termasuk key GitHub App yang terpasang di seluruh organisasi.
Tulisan ini membahas stopgap di hari yang sama, perpindahan ke worker terisolasi dengan izin cloud yang terbatas, cut-over dengan outage tercatat sekitar 9 menit, bug scaling yang ditemukan saat rollout, dan update tool yang kemudian mematahkan jaminan stopgap tersebut.
Audit itu menemukan dan mereproduksi masalahnya sebelum platform dipakai lebih luas.
Scanner yang berjalan di dalam API
Pris.AI adalah platform keamanan kode berbasis AI yang saya bangun dan operasikan di Metrodata (MII). Sebuah GitHub App menghubungkan repository di organisasi. Setiap pull request menjalankan delapan security engine open-source, model AI melakukan triage temuan, dan fix agent bisa membuka pull request berisi usulan perbaikan. Per 24 Oktober, platform ini sudah mengirim hasil scan ke 628 pull request di 24 repository.
Versi pertama, pada Juni, menjalankan scan secara inline, di thread milik backend API. Itu kompromi karena izin. Hak deployment saya hanya Contributor, sementara menjalankan job terpisah butuh principal dengan role assignment yang tidak bisa saya buat. Executor inline membuat produk ini bisa dirilis.
Konsekuensinya, isi repository diproses di proses yang sama, dengan environment yang sama, seperti API yang memegang secret platform.
Yang direproduksi audit
Pada akhir September, sebelum platform dipakai lebih luas, saya meminta audit kesiapan komersial: tiga belas review spesialis, ditambah review red-team dan sintesis. Temuan teratasnya adalah eksekusi kode di proses API.
Jalurnya biasa saja. Saat fix agent meng-upgrade dependency Python, agent menjalankan uv lock. Tanpa --no-build, uv bisa mem-build project lokal untuk membaca metadata-nya, yang berarti menjalankan setup.py milik project itu. Repository bisa menaruh kode apa pun di sana, dan kode itu akan berjalan dengan seluruh environment backend. Audit berhasil mereproduksinya.
Ada jalur lain yang berkaitan. Beberapa scanner memuat konfigurasi dari repository yang sedang di-scan. Konfigurasi Checkov di repository bisa menunjuk ke direktori external checks, dan gitleaks otomatis memuat file ignore dari source yang di-scan, yang bisa menyembunyikan temuan.
Audit yang sama menemukan masalah lain, dan semuanya diperbaiki dalam rangkaian kerja yang sama: setiap pengguna Entra bisa menjadi admin, chat agent bisa men-suppress temuan, dan token GitHub level organisasi muncul di argumen proses.
Stopgap, pagi itu juga
Perbaikan pertama masuk pada pagi 25 September. Tujuannya menghentikan risiko secepatnya, bukan menjadi batas keamanan yang sebenarnya:
uv lockdanuv exportberjalan dengan--no-build. Project yang butuh build dilaporkan sebagai "could not upgrade safely", dan perubahan parsial dikembalikan. bun tetap memakai--ignore-scripts.- Setiap subprocess scanner dan fix mendapat environment berbasis allowlist. Variabel yang namanya seperti key, token, dan secret, endpoint managed identity, serta setting Azure dan GitHub dibuang.
- Scanner memakai konfigurasi milik platform. File konfigurasi Checkov dan gitleaks dari repository dihapus dari hasil clone, karena saya sudah memverifikasi bahwa flag command line saja tidak mencegah konfigurasi repository dimuat.
- Kill switch bisa mematikan fix pull request sebelum ada yang dibaca atau dijalankan.
Acceptance test memakai fixture repository yang setup.py-nya menulis file penanda. Setelah perubahan, tidak ada penanda yang tertulis, dan dump environment dari child process sungguhan tidak berisi variabel secret.
Flag hanya sebaik tool yang mematuhinya. Batas keamanan yang sebenarnya harus berada di arsitektur.
Isolasi tanpa izin baru
Sore di hari yang sama, arsitektur barunya sudah ada di branch:
- API menaruh pesan di queue Service Bus. Pesan itu hanya membawa claim token sekali pakai.
- KEDA melihat queue tersebut dan menjalankan Container Apps Job yang terisolasi.
- Job mengklaim pekerjaannya dari API dengan compare-and-swap dan lease. Klaim itu mengembalikan bearer token per job dan token GitHub baru yang dibatasi untuk satu repository dan satu tujuan.
- Di dalam job, tool repository berjalan di sandbox: user ID diturunkan, resource dibatasi, process group bisa di-kill, dan pembacaan file tidak mengikuti symlink. Clone dibatasi, dan ukuran repository diperiksa lebih dulu.
- Hasilnya dikirim kembali ke API lewat bearer milik job itu sendiri. Pesan yang rusak atau tidak sah masuk ke dead-letter queue.
Di production, control plane menolak scan inline dengan 503, dengan emergency override yang harus diset secara sengaja.
Batas izin Contributor membentuk desain ini. Saya tidak bisa memberi role ke worker, jadi worker di-deploy tanpa managed identity sama sekali. Sebuah validation script memastikan definisi worker bebas kredensial. Setiap job hanya memegang apa yang dikembalikan klaimnya, untuk satu repository. Role assignment-nya sudah ditulis di Bicep di balik flag, menunggu pemberian role yang masih tertunda saat terakhir saya cek.
Sisa hardening berjalan paralel: identity assertion bertanda tangan berumur 30 detik yang terikat ke body request dari web tier, kontrol akses dengan role tetap, sign-in khusus Entra, dan session server-side yang bisa dicabut. Totalnya 14 item prioritas, selesai dalam sekitar satu setengah hari lewat stacked branch. Saya menulis spec, mengarahkan AI coding agent untuk sebagian besar implementasi, serta me-review, menguji di Azure, dan me-merge setiap perubahan.
Cut-over, dan worker yang tidak pernah berjalan
Rollout berlangsung pada 26 dan 27 September. Backend mati sekitar 9 menit saat berpindah dari mode inline ke mode queue. Ini tool internal; untuk produk pelanggan, itu tidak bisa diterima.
Lalu tidak ada scan yang berjalan. Belum ada satu pun eksekusi worker yang dimulai di production. Scaler Service Bus milik KEDA membaca jumlah pesan di queue dari management endpoint, dan itu butuh klaim Manage. Scale rule-nya memakai key Listen-only milik worker, jadi setiap polling mendapat 401.
Perbaikan yang paling gampang adalah memberi worker key Manage, dan itu salah. Sebagai gantinya, ada rule terpisah per queue dengan hak Manage yang hanya dipakai untuk autentikasi scaler. Container tetap terhubung dengan key Listen-only, sehingga kode repository tidak pernah melihat key yang bisa mengelola queue. Identity khusus untuk scaler sudah disiapkan untuk saat role assignment memungkinkan.
Setelah perbaikan itu, latihan dead-letter memastikan pesan beracun mendarat di dead-letter queue dan email alert-nya sampai.
Jaminannya tetap patah
Pada 1 Oktober, CI mengambil versi uv yang lebih baru. Versi 0.12.20 dan 0.12.21 menjalankan setup.py milik project lokal saat uv lock --no-build jika project mendeklarasikan metadata dinamis. Jaminan stopgap tidak lagi berlaku untuk upgrade dependency oleh fix agent.
Perbaikannya tidak lagi bergantung pada uv. Sebuah pre-check menolak project dengan metadata dinamis, project lama berbasis setup.py atau setup.cfg, serta anggota workspace seperti itu sebelum uv dijalankan, dan uv di-pin ke 0.12.19 di image maupun CI.
Inilah alasan isolasi lebih penting daripada flag. Flag adalah janji dari tool pihak ketiga, dan janji itu berubah di rilis minor.
Batasan, dan yang akan saya lakukan secara berbeda
- Buat threat model sejak deploy pertama. Executor inline adalah kompromi yang masuk akal untuk urusan izin pada Juni. Saat itu juga seharusnya ada threat model tertulis, bukan audit tiga bulan kemudian.
- Rencanakan cut-over tanpa downtime. Menjalankan mode lama dan baru berdampingan, lalu memindahkan traffic setelah scan pertama lewat queue berhasil, kemungkinan besar bisa menghindari 9 menit itu.
- Buktikan seluruh jalur sebelum berpindah. Satu pemeriksaan end to end, "scan yang masuk queue menjalankan job dan mengembalikan temuan", akan menangkap bug scaler sebelum production, bukan sesudahnya.
- Pin tool yang menegakkan keamanan. Jaminan
--no-buildbergantung pada versi tool yang tidak di-pin. Sekarang uv di-pin dan pre-check berjalan sebelumnya, jadi rilis baru tidak mengubah apa pun sampai ada yang memindahkan pin dengan sengaja. - Auditnya internal. Cakupannya luas, tetapi itu bukan penetration test independen.
Flag mengurangi risiko dalam hitungan jam. Isolasi yang membuat sisa risikonya lebih kecil: kode repository sekarang berjalan di job yang hanya memegang token untuk satu repository, bukan di proses yang memegang key.