Satu Baris Kode yang Salah Ketik, dan Endpoint Debug yang Nganggur Terbuka 14 Bulan

Baris pertama di laporan PDF dari firma audit itu yang bikin aku berhenti scroll: "Endpoint /internal/seed-status dapat diakses tanpa autentikasi dari jaringan publik." Aku baca ulang tiga kali sebelum ngecek timestamp log akses — endpoint itu, yang harusnya cuma hidup di laptop development-ku sendiri, ternyata sudah nganggur terbuka di produksi selama 14 bulan. Bukan bocor minggu lalu. Empat belas bulan. Lebih lama dari umur beberapa fitur yang sudah kami ship dan matikan lagi.

Endpoint yang Dibuat untuk Sekali Pakai

Ceritanya dimulai jauh sebelum audit itu. Waktu kami baru pindah dari seed data manual ke script onboarding otomatis, aku butuh cara cepat buat ngecek apakah proses seeding di staging berjalan benar tanpa harus login ke database lewat terminal setiap kali. Solusinya kelihatan sepele: satu route kecil, /internal/seed-status, yang cuma nge-dump jumlah baris di beberapa tabel kunci — bukan isi datanya, cuma angka. Aman, pikirku, karena cuma angka, dan aku bungkus pakai pengecekan environment biar enggak nyala di produksi.

Kode pengecekannya kelihatan beres sepintas:

if (process.env.NODE_ENV = 'production') {
  // skip route registration
} else {
  app.get('/internal/seed-status', debugHandler);
}

Masalahnya ada di satu karakter yang kelewatan: = seharusnya ===. Baris itu bukan membandingkan, tapi langsung menetapkan nilai NODE_ENV jadi string 'production' — dan sebuah assignment di JavaScript selalu mengembalikan nilai yang di-assign, yang di sini adalah string non-kosong, yang selalu truthy. Jadi cabang if itu selalu benar, isinya cuma komentar kosong "skip route registration" yang enggak ngerjain apa-apa, dan cabang else yang nyalain endpoint debug... ya enggak pernah ketrigger. Route-nya malah ketimpa diregistrasi di tempat lain lewat jalur yang enggak kena pengecekan ini sama sekali, karena struktur routing-nya ternyata lebih berantakan dari yang aku kira waktu nulis cepat-cepat jam 10 malam itu.

Kenapa Enggak Ada yang Nyadar Selama Setahun Lebih

Yang paling bikin aku malu bukan bug-nya — bug satu karakter itu bisa kejadian ke siapa aja. Yang bikin malu adalah enggak ada satu pun lapisan yang nangkep ini sebelum audit eksternal. Code review waktu itu fokus ke logika seeding-nya sendiri, bukan ke satu baris kecil pengecekan environment yang kelihatan "sudah jelas benar". Linter kami enggak dikonfigurasi buat nangkep assignment di dalam kondisi if — aturan no-cond-assign ada di ESLint, tapi entah kenapa enggak pernah diaktifkan di config kami, kemungkinan karena di-skip waktu setup awal biar enggak terlalu berisik.

Monitoring juga enggak nolong, karena endpoint ini enggak pernah dipanggil siapa-siapa secara normal — enggak ada traffic pattern yang aneh buat dicurigai, enggak ada error rate yang naik, enggak ada apa pun yang nongol di dashboard. Endpoint yang eksis tapi enggak pernah dipakai itu invisible buat alerting apa pun yang kami punya, karena semua alert kami dibuat buat ngedeteksi sesuatu yang berubah, bukan sesuatu yang diam-diam selalu ada.

Orang menggunakan MacBook Pro
Orang menggunakan MacBook Pro

Bagaimana Auditornya Nemuin, dan Apa yang Terjadi Setelahnya

Yang nemuin bukan pentester mahal yang kami sewa sendiri — kami belum sampai di titik budget buat itu. Ini audit keamanan vendor standar yang diminta sama calon klien enterprise sebelum nandatangan kontrak tahunan, bagian dari proses procurement mereka. Firma yang mereka tunjuk cuma jalanin endpoint enumeration dasar: scan daftar path yang umum dipakai developer buat debugging (/debug, /internal, /_status, dan variasinya), lalu cek mana yang merespons tanpa autentikasi. Enggak ada exploit canggih. Cuma checklist.

Begitu laporan itu masuk, hal pertama yang aku kerjakan bukan nutup endpoint-nya — itu gampang, satu baris hapus plus deploy, selesai dalam lima belas menit. Yang makan waktu adalah nge-audit log akses selama 14 bulan ke belakang buat mastiin enggak ada pihak lain yang nemuin dan manfaatin route itu lebih dulu. Untungnya, karena endpoint-nya cuma ngembaliin angka jumlah baris tabel — enggak ada email, enggak ada token, enggak ada apa pun yang bisa langsung disalahgunakan — dan log akses cuma nunjukkin traffic dari range IP firma audit itu sendiri, enggak ada jejak akses mencurigakan sebelumnya. Keberuntungan, bukan desain yang bagus, dan aku sadar betul bedanya.

MacBook dengan baris-baris kode di layarnya di atas meja kerja yang sibuk
MacBook dengan baris-baris kode di layarnya di atas meja kerja yang sibuk

Perubahan yang Sebenarnya, Bukan Cuma Nambah Satu Aturan Linter

Nambah no-cond-assign ke ESLint cuma butuh satu baris config, dan aku langsung lakuin hari itu juga. Tapi kalau cuma itu yang berubah, aku enggak percaya diri ini enggak akan kejadian lagi dalam bentuk lain. Jadi kami ubah dua hal yang lebih struktural: pertama, semua route yang sifatnya debug/internal sekarang wajib didaftarkan di file terpisah yang di-import secara eksplisit cuma di entry point development, bukan dicampur jadi satu pengecekan if di tengah file routing produksi — jadi secara arsitektur enggak mungkin ketimpa salah gara-gara satu kondisi yang gagal. Kedua, kami nambah satu baris di checklist pre-launch kuartalan: coba akses setiap path yang "harusnya cuma buat dev" langsung dari luar jaringan kantor, bukan cuma percaya pada kode yang bilang begitu.

Yang paling aku bawa pulang dari ini bukan soal syntax JavaScript. Ini soal betapa mudahnya sesuatu yang "cuma sementara, cuma buat testing" diam-diam jadi permanen, dan betapa invisible-nya sesuatu yang enggak pernah dipanggil — karena sistem monitoring apa pun yang kami bangun selalu dirancang buat ngedeteksi anomali, bukan ngedeteksi sesuatu yang diam-diam normal sejak awal.

Takeaway

Kode yang "kelihatan jelas benar" justru yang paling jarang diperiksa ulang — satu karakter yang salah ketik bisa nganggur tak terdeteksi selama lebih dari setahun kalau tidak ada yang secara aktif mencoba membongkarnya dari luar. Linter rules yang kelihatan remeh (seperti no-cond-assign) ada karena alasan nyata, bukan sekadar formalitas; dan audit keamanan dari pihak luar, sekecil apa pun pemicunya, kadang jadi satu-satunya lapisan yang benar-benar menguji asumsi "ini aman karena sudah dicek" — bukan menggantikannya.

---

Sumber gambar: Ales Nesetril di Unsplash Sumber gambar: Glenn Carstens-Peters di Unsplash Sumber gambar: Christopher Gower di Unsplash

The conversation 0

No comments yet. Start the conversation.

Comments are reviewed before appearing. Links are masked.