Satu Pintu Jejak Satu ID: Alat Audit yang Tak Bisa Dibohongi
Reviewer menemukan read() mengikuti symlink ke .git dan lint yang cocok lintas berkas. Pelajaran soal verifier yang tidak boleh bisa dibohongi.
Ringkasan
Alat audit baca-saja ini nemu dua bug serius. Fungsi read() ikut symlink sehingga bisa baca .git/ sembarangan, tapi daripada ribet resolve path, mending tolak semua symlink langsung biar nggak kena TOCTOU. Terus korpus lint digabung tanpa pemisah bikin ID nyambung dua berkas, solusinya cuma nambah newline per berkas.
Dua temuan datang dari satu sesi review pada alat audit kecil. Pertama, fungsi read() yang bertugas membaca berkas jejak ternyata mau mengikuti symlink ke mana pun, termasuk ke dalam .git/. Kedua, korpus lint untuk kartu Done menggabungkan berkas dengan string kosong, sehingga separuh ID di ujung berkas A bisa menempel dengan separuh ID di awal berkas B tanpa pernah terlihat sebagai dua berkas berbeda.
Tebakan pertama terasa benar: selesaikan symlink dengan realpath, lalu periksa bahwa hasilnya masih berada di dalam direktori yang diizinkan. Dokumentasi Python memang menyediakan prinsipnya: os.path.realpath mengembalikan jalur kanonik dengan menghilangkan symlink yang ditemui di sepanjang jalur [3].
Menolak Terbukti Lebih Kuat daripada Menyelesaikan
Untuk alat audit yang bersifat baca-saja, jalur penolakan justru lebih unggul. Alih-alih mencoba menyelesaikan tautan, fungsi tersebut kini menolak semua symlink secara langsung dengan mengembalikan string kosong. Tidak ada resolusi, tidak ada celah waktu antara resolusi dan pembukaan berkas, serta tidak ada risiko regresi. Keputusan ini berakar pada prinsip keamanan yang mapan. CWE-59 mendefinisikan resolusi tautan yang tidak tepat sebelum akses berkas sebagai kerentanan, di mana akses ke berkas berdasarkan nama dapat dialihkan melalui tautan ke sumber daya yang tidak dimaksudkan [1]. Konsekuensinya mencakup pembacaan atau modifikasi berkas yang tidak diinginkan, serta bypass mekanisme perlindungan.
Dokumentasi symlink(7) menjelaskan bahwa tautan simbolis adalah penunjuk ke sebuah NAMA, bukan ke objek yang mendasarinya, dan tautan tersebut dapat merujuk pada jalur yang tidak ada [2]. Tautan semacam itu bahkan punya nama sendiri: dangling link. Inilah alasan mengapa fungsi read() yang permisif dapat dialihkan secara diam-diam. Jika tautan diubah tepat setelah pemeriksaan jalur kanonik tetapi sebelum berkas dibuka, alat audit tetap dapat membaca berkas di luar direktori yang diizinkan. Oleh karena itu, alat ini memilih penolakan mutlak secara sengaja. Alat baca-saja tidak mendapatkan keuntungan apa pun dari mengikuti tautan.
Korpus Tanpa Pembatas Membuat Verifier Berbohong
Celah kedua lebih halus. Ketika korpus teks dari berbagai berkas digabungkan dengan string kosong, batas antarberkas menjadi tidak terlihat oleh mesin. Berkas yang berakhir dengan karakter B-12 dan berkas berikutnya yang diawali 3 akan menghasilkan string B-123. Verifier kemudian mencatat kecocokan untuk ID yang tidak pernah ada. Ini adalah false positive yang merusak integritas audit: verifier yang melaporkan sukses di atas bukti rusak jauh lebih berbahaya daripada verifier yang gagal keras.
Solusinya satu karakter: pemisah baris baru per berkas saat korpus dirangkai. Batas antarberkas kembali terlihat, dan ID yang menyambung dua berkas tidak lagi mungkin cocok.
Satu ID, Satu Perintah, Seluruh Rantai
Alat ini berdiri di atas disiplin yang lebih besar. Setiap kartu yang ditandai Done wajib meninggalkan jejak literal berbentuk kode kartu di bawah direktori buku besar proyek; lint DONE-FOOTPRINT menolak kartu tanpa jejak, dengan pengecualian untuk arsip History. Ketika sebuah ID perlu diverifikasi, trace.py menelusurinya ke semua pintu sekaligus: BOARD untuk status tugas, DECISIONS untuk konteks keputusan, berkas issue untuk riwayat, folder temuan QA dan hasil pemindaian untuk bukti, sampai commit git untuk perubahan kode aktual. Satu perintah, seluruh rantai.
NIST merumuskan prinsip yang sama untuk bukti forensik: chain of custody adalah proses yang melacak pergerakan bukti sepanjang siklus pengumpulan, pengamanan, dan analisis dengan mendokumentasikan setiap pihak yang menanganinya [4]. Pembukuan rekayasa perangkat lunak bisa meminjam disiplin itu dengan satu keunggulan: bukti versi rekayasa bisa diperiksa mesin. Rantai yang valid adalah rantai yang bisa ditelusuri ulang oleh skrip, bukan yang diceritakan ulang oleh manusia.
Syaratnya satu: alat pemeriksanya sendiri tidak boleh bisa dibohongi. Batas baca sekitar lima MB per berkas menutup vektor penghabisan memori. Penolakan symlink menutup pengalihan jalur. Pemisah baris baru menutup ID yang menyambung berkas. Tiga perbaikan kecil pada hari yang sama, dan rantai buktinya kembali layak dipercaya.
Sumber
[1] CWE-59: Improper Link Resolution Before File Access (Link Following)
[2] symlink(7) - Linux manual page
[3] os.path - Common pathname manipulations, Python documentation
[4] Chain of Custody - NIST CSRC Glossary