Audit session dan secret saat migrasi runtime ke Bun dan Rust perlu dilakukan sejak awal, bukan setelah insiden terjadi. Ketika sebagian request masih diproses runtime lama dan sebagian lain mulai berpindah ke Bun atau komponen Rust, risiko utamanya bukan hanya bug fungsional, tetapi perbedaan perilaku keamanan: format token berubah, cookie tidak konsisten, hashing atau enkripsi tidak kompatibel, dan error handling membocorkan detail sensitif.

Artikel ini membahas panduan audit praktis untuk backend yang bermigrasi bertahap, dengan fokus pada auth, session, secret handling, validasi input, cookie flags, rotasi key, perbedaan env/config, logging sensitif, upload abuse, dan rate limit. Tujuannya bukan memilih runtime “terbaik”, melainkan memastikan dua runtime yang hidup berdampingan tidak membuka celah baru.

Mengapa migrasi bertahap ke Bun dan Rust berisiko untuk session dan secret

Pada migrasi bertahap, sistem lama dan sistem baru biasanya berjalan bersamaan untuk waktu tertentu. Sebagian endpoint mungkin masih dilayani runtime lama, sementara endpoint lain dialihkan ke Bun, dan fungsi tertentu seperti verifikasi token, image processing, atau worker dipindahkan ke Rust. Model ini praktis, tetapi menciptakan beberapa sumber risiko:

  • Asumsi keamanan tidak sama antar runtime dan library.
  • Representasi data berbeda, misalnya encoding base64url, format tanggal, urutan serialisasi JSON, atau nama header/cookie.
  • Konfigurasi tersebar di file env, secret manager, config build, dan variabel runtime yang tidak selalu sinkron.
  • Fallback logic berbahaya, misalnya “jika verifikasi token gagal di layanan baru, coba validasi dengan parser lama”.
  • Observabilitas pecah, sehingga sulit membedakan error validasi biasa dengan indikasi serangan.

Dalam konteks keamanan, masalah terbesar biasanya bukan karena Bun atau Rust “kurang aman”, melainkan karena inkonsistensi implementasi saat dua stack harus menerima input yang sama dan menghasilkan keputusan keamanan yang sama.

Ruang lingkup audit: apa saja yang wajib diperiksa

1. Auth dan format token

Jika sistem menggunakan session cookie, JWT, opaque token, atau signed token, audit harus menjawab pertanyaan berikut:

  • Apakah runtime lama dan runtime baru menggunakan algoritma yang sama untuk sign/verify?
  • Apakah format claim, waktu kedaluwarsa, issuer, audience, dan subject diperlakukan sama?
  • Apakah perbedaan encoding menyebabkan token valid di satu runtime tetapi ditolak atau salah diparse di runtime lain?
  • Apakah token yang dibuat di runtime baru masih dapat diverifikasi dengan aman oleh runtime lama selama masa transisi?

Masalah umum saat migrasi adalah serialisasi payload berbeda. Misalnya, runtime lama membuat signed payload dari JSON yang urutan field-nya konsisten, sementara layanan baru membangun string sign dengan urutan field berbeda. Jika format sign bergantung pada representasi mentah, token bisa gagal diverifikasi meskipun datanya identik secara logis.

Catatan: Hindari membuat format token kustom bila tidak perlu. Jika sudah terlanjur ada, dokumentasikan format byte-level atau canonicalization rule secara eksplisit sebelum diimplementasikan ulang di Bun atau Rust.

2. Session storage dan invalidation

Jika session disimpan di Redis, database, atau store lain, audit perlu memverifikasi:

  • Apakah key naming convention sama?
  • Apakah TTL, sliding expiration, dan revocation bekerja sama di semua runtime?
  • Apakah logout dari endpoint yang dilayani runtime baru benar-benar menghapus session yang dibuat runtime lama?
  • Apakah ada race condition ketika dua runtime memperbarui session yang sama?

Bug klasik: runtime lama memperpanjang TTL session setiap request, tetapi runtime baru hanya memvalidasi tanpa memperbarui TTL. Akibatnya user terlihat aktif, tetapi session mendadak kedaluwarsa jika request terakhir banyak diarahkan ke layanan baru.

3. Secret handling dan rotasi key

Secret yang perlu diaudit mencakup signing key, encryption key, API credential, DSN, cookie secret, pepper untuk password, dan secret untuk integrasi internal. Risiko yang sering muncul:

  • Nama variabel env berbeda, sehingga service baru memakai secret default atau kosong.
  • Format key berbeda, misalnya satu layanan mengharapkan raw bytes, layanan lain menerima string base64 tanpa decode yang benar.
  • Rotasi key tidak sinkron, sehingga token dari layanan A belum bisa diverifikasi layanan B.
  • Secret bocor di log karena panic, stack trace, atau debug output.

Pendekatan aman saat rotasi adalah memisahkan key for signing dan key set for verification. Runtime yang menerbitkan token cukup menggunakan key aktif, tetapi semua runtime yang memverifikasi token harus sementara menerima beberapa key yang masih valid selama masa transisi.

4. Validasi input dan parser mismatch

Bun dan Rust mungkin menggunakan parser, validator, atau binding yang berbeda dari runtime lama. Dampaknya bisa serius jika satu runtime menerima input yang sebenarnya harus ditolak. Audit perlu memeriksa:

  • Perbedaan handling untuk string kosong, null, array tunggal vs array jamak, integer overflow, unicode, dan duplicate parameter.
  • Perbedaan parser pada multipart upload, query string, form body, dan JSON.
  • Apakah batas ukuran body, jumlah field, kedalaman JSON, dan timeout request sama?

Contoh: runtime lama menolak field role selain enum tertentu, tetapi binding di service Rust menganggap field yang tidak dikenal sebagai nilai default. Jika endpoint ini mempengaruhi otorisasi, hasilnya bisa menjadi privilege escalation atau bypass policy.

5. Cookie flags dan domain/path scope

Cookie sering rusak diam-diam saat migrasi sebagian karena endpoint lama dan baru berada di host, path, atau proxy chain berbeda. Audit harus memastikan:

  • HttpOnly aktif untuk cookie session.
  • Secure aktif pada lingkungan HTTPS.
  • SameSite sesuai kebutuhan aplikasi, terutama jika ada flow login lintas domain.
  • Domain dan Path tidak terlalu luas atau terlalu sempit.
  • Nama cookie tidak bentrok antara sistem lama dan baru.

Kesalahan kecil seperti path cookie berbeda dapat menyebabkan request ke endpoint baru tidak membawa session yang sama, lalu tim menambahkan fallback tidak aman seperti menyalin token ke header dari JavaScript.

6. Logging sensitif dan error handling

Saat service baru masih sering di-debug, kecenderungannya adalah meningkatkan verbosity log. Ini berbahaya bila body auth, cookie, Authorization header, atau stack trace ikut tercatat. Audit harus memeriksa:

  • Apakah token, cookie, password, OTP, dan secret disensor sebelum log dikirim?
  • Apakah error dari parser kripto, database, atau dependency internal diekspos ke klien?
  • Apakah panic di komponen Rust diterjemahkan menjadi respons generik yang aman?
  • Apakah mode debug bisa aktif tanpa sengaja di staging atau production?

Jangan menganggap “hanya staging” aman. Banyak kebocoran secret dimulai dari log staging, terutama bila memakai data produksi tersanitasi sebagian atau token uji yang memiliki akses nyata ke sistem internal.

7. Upload abuse dan rate limit

Migrasi endpoint upload ke runtime baru perlu audit khusus karena parser file, limit ukuran, dan buffering bisa berubah. Periksa hal berikut:

  • Batas ukuran file dan jumlah part konsisten.
  • Tipe file tidak hanya dipercaya dari Content-Type klien.
  • Nama file, path, dan metadata dibersihkan sebelum digunakan.
  • Proses scanning atau validasi file tetap berjalan meskipun endpoint dipindah.
  • Rate limit diterapkan di layer yang sama untuk runtime lama dan baru.

Masalah umum: rate limit hanya dipasang pada route lama di API gateway, sedangkan route baru ke Bun melewati jalur berbeda tanpa pembatasan. Penyerang cukup menemukan endpoint baru untuk melakukan brute force login, abuse upload, atau enumeration.

Risiko spesifik saat dua runtime hidup berdampingan

Format token berbeda

Perbedaan kecil seperti canonicalization JSON, base64url padding, atau penanganan waktu kedaluwarsa dapat memicu perilaku tidak konsisten. Satu runtime mungkin menerima token dengan claim waktu bertipe string, runtime lain mengharuskan integer. Jika verifikasi gagal, tim kadang menambahkan fallback parser yang justru memperluas permukaan serangan.

Hashing atau cipher mismatch

Untuk password, API secret, atau data terenkripsi, masalah yang sering muncul adalah:

  • Format hash berbeda sehingga verifikasi gagal setelah login diarahkan ke runtime lain.
  • Encoding input sebelum hashing berbeda, misalnya whitespace, unicode normalization, atau charset.
  • Nonce/IV/tag untuk encryption diproses dengan urutan atau format berbeda.

Jangan migrasikan algoritma hash atau skema enkripsi bersamaan dengan migrasi runtime kecuali benar-benar direncanakan. Ubah satu dimensi terlebih dahulu agar sumber masalah mudah diisolasi.

Session invalidation tidak konsisten

Jika logout, revoke token, reset password, atau force re-auth tidak bekerja sama di semua runtime, pengguna yang seharusnya sudah keluar bisa tetap memiliki akses di salah satu jalur. Ini sering terjadi saat runtime baru memiliki cache internal yang tidak ikut mendengar event invalidasi dari sistem lama.

Drift konfigurasi

Drift konfigurasi adalah kondisi ketika dua service tampak memiliki setelan “sama”, tetapi nilai efektifnya berbeda. Contohnya:

  • Clock skew timeout berbeda.
  • Header proxy trust berbeda, sehingga penentuan HTTPS salah dan cookie Secure tidak diset.
  • Origin allowlist CORS berbeda.
  • Rate limit key extractor berbeda, misalnya satu memakai IP asli, satu memakai IP proxy.

Drift seperti ini sulit dideteksi dari unit test biasa; perlu verifikasi end-to-end di staging yang meniru topologi produksi.

Error handling yang bocor

Saat parser atau validator baru menolak request, pesan error sering lebih detail daripada sistem lama. Respons seperti “invalid signature length”, “unknown key id”, atau “failed to parse JWT header” memberi informasi yang membantu attacker memetakan implementasi Anda. Gunakan pesan generik untuk klien, detail lengkap hanya di log internal yang telah disanitasi.

Pola implementasi yang lebih aman

Gunakan kontrak lintas runtime untuk auth dan session

Sebelum menulis ulang kode, tentukan kontrak yang eksplisit:

  • Format token dan claim wajib.
  • Aturan verifikasi waktu: issued at, expiration, clock skew.
  • Skema penyimpanan session: key, TTL, revocation marker.
  • Daftar cookie beserta flag, domain, dan path.
  • Daftar error code eksternal yang boleh terlihat ke klien.

Kontrak ini sebaiknya diuji dengan cross-runtime test vectors: token dibuat di runtime A, diverifikasi di runtime B, dan sebaliknya.

Pisahkan penerbitan dan verifikasi key saat rotasi

Model sederhana yang aman:

  1. Sediakan key aktif untuk signing baru.
  2. Sediakan key lama + key aktif untuk verification.
  3. Tambahkan identifier key bila format token mendukung.
  4. Setelah semua token lama kedaluwarsa, hapus key lama dari verification set.

Ini mencegah putus akses massal saat sebagian layanan sudah memakai key baru dan sebagian lain belum.

Normalisasi validasi input di edge atau shared library

Bila memungkinkan, letakkan aturan validasi dasar di layer yang konsisten, misalnya API gateway, schema validation terpusat, atau shared contract. Tujuannya bukan menghilangkan validasi di service, tetapi mengurangi perilaku berbeda antar runtime untuk input yang sama.

Standarkan sanitasi log

Buat daftar field sensitif yang wajib disensor di semua service, misalnya:

  • Authorization
  • Cookie
  • Set-Cookie
  • password, otp, token, secret, apiKey
  • session_id, refresh_token

Sanitasi sebaiknya dilakukan sedekat mungkin dengan sumber log, bukan hanya di pipeline agregasi.

Contoh skenario bug yang realistis

Skenario 1: Logout berhasil, tetapi session masih aktif di jalur lama

Aplikasi memindahkan endpoint /logout ke Bun. Endpoint ini menghapus cookie dan menandai session revoked di Redis dengan key baru. Namun middleware pada runtime lama masih memeriksa key session lama tanpa melihat marker revoke baru. Hasilnya, user tampak logout di UI, tetapi request ke endpoint lama masih lolos.

Cara audit: lakukan login, akses endpoint lama dan baru, lalu logout melalui jalur baru. Verifikasi semua endpoint setelah logout benar-benar menolak session yang sama.

Skenario 2: Token refresh gagal acak setelah rotasi key

Layanan Rust mulai menandatangani refresh token dengan key baru. Service lama yang masih menangani sebagian refresh request belum memuat verification set terbaru. Sebagian user gagal refresh dengan error 401, lalu client melakukan login ulang terus-menerus.

Akar masalah: rotasi key diterapkan sebagai penggantian penuh, bukan mode kompatibilitas sementara.

Skenario 3: Cookie Secure hilang di belakang proxy

Service baru menentukan apakah request HTTPS berdasarkan header yang tidak dipercaya karena konfigurasi proxy trust belum benar. Akibatnya cookie session dikeluarkan tanpa flag Secure pada production. Secara fungsional login tetap berjalan, tetapi cookie menjadi lebih mudah terekspos di jalur yang tidak semestinya.

Skenario 4: Parser multipart baru membuka upload abuse

Runtime lama membatasi ukuran total body dan jumlah file, sedangkan service baru hanya membatasi ukuran per file. Penyerang mengirim banyak part kecil untuk menghabiskan memori atau disk sementara rate limit belum aktif di route baru.

Contoh pengujian praktis di staging

Berikut contoh daftar verifikasi yang berguna sebelum go-live. Anda tidak harus memakai tool tertentu, yang penting skenario diuji terhadap jalur lama dan baru.

# 1. Login melalui endpoint lama, akses endpoint baru dengan session yang sama
# Harus berhasil jika memang kompatibel selama masa transisi.

# 2. Login melalui endpoint baru, akses endpoint lama
# Verifikasi cookie, token, dan claim dipahami konsisten.

# 3. Logout dari endpoint baru, lalu ulangi akses ke endpoint lama dan baru
# Keduanya harus menolak session/token yang sama.

# 4. Rotasi key di staging
# Token lama tetap bisa diverifikasi sampai masa transisi berakhir.
# Token baru tidak boleh gagal di service verifier mana pun.

# 5. Kirim input invalid yang sama ke kedua runtime
# Status code, kategori error, dan sanitasi respons harus setara.

# 6. Uji upload besar, banyak part, dan content-type palsu
# Pastikan limit, timeout, dan sanitasi berlaku sama.

# 7. Uji rate limit pada endpoint auth dan upload
# Pastikan route baru tidak melewati policy lama.

Jika memungkinkan, buat golden test untuk token dan session:

  • Satu set token valid/invalid yang harus menghasilkan keputusan verifikasi yang sama di semua runtime.
  • Satu set cookie dan session record yang diuji untuk expiry, revocation, dan renewal.
  • Satu set payload invalid untuk memeriksa konsistensi parser dan validator.

Checklist audit sebelum go-live

  1. Auth contract terdokumentasi: format token, claim, expiry, issuer, audience, clock skew.
  2. Cross-runtime verification lulus: token dari runtime lama dan baru saling kompatibel sesuai desain.
  3. Session invalidation konsisten: logout, revoke, reset password, dan force logout bekerja di semua jalur.
  4. Cookie flags benar: HttpOnly, Secure, SameSite, Domain, Path diverifikasi dari respons nyata di staging.
  5. Secret source tunggal atau terkontrol: tidak ada default secret tersembunyi atau fallback yang diam-diam aktif.
  6. Rotasi key diuji: signing key aktif dan verification set transisional berjalan tanpa memutus user valid.
  7. Input validation parity: request invalid ditolak konsisten, tanpa acceptance gap.
  8. Upload controls aktif: ukuran body, jumlah part, timeout, penyimpanan sementara, dan scanning diverifikasi.
  9. Rate limit konsisten: endpoint auth, refresh, reset password, dan upload tidak memiliki route bypass.
  10. Logging disanitasi: token, secret, cookie, password, dan body sensitif tidak muncul di log.
  11. Error response aman: klien menerima pesan generik, detail teknis hanya di log internal.
  12. Konfigurasi proxy dan HTTPS benar: terutama untuk penentuan cookie Secure dan IP rate limit.
  13. Observabilitas siap: metrik, log, dan tracing dapat membedakan jalur runtime lama vs baru.
  14. Rollback jelas: ada cara mengalihkan traffic kembali tanpa merusak session atau token yang sudah terbit.

Observabilitas pasca-rilis: apa yang harus dipantau

Setelah rilis, jangan hanya memantau error rate umum. Untuk migrasi keamanan, metrik yang lebih berguna adalah:

  • Tingkat kegagalan login per runtime dan per route.
  • Jumlah token verify gagal dikelompokkan menurut alasan umum yang sudah dinormalisasi.
  • Session revoke mismatch: indikasi request lolos setelah logout atau reset password.
  • Distribusi response code pada endpoint auth, refresh, upload, dan reset password.
  • Rate limit trigger per jalur runtime untuk melihat bypass.
  • Ukuran upload dan timeout untuk mendeteksi abuse atau perubahan perilaku parser.
  • Panic atau unhandled exception di komponen Rust dan service Bun.

Sebaiknya tambahkan dimensi atau label yang menunjukkan request diproses oleh runtime lama, Bun, atau komponen Rust. Tanpa ini, lonjakan 401 atau 400 akan sulit ditelusuri ke jalur migrasi tertentu.

Praktik yang membantu: aktifkan canary release atau persentase traffic kecil lebih dulu, lalu bandingkan metrik auth/session antara jalur lama dan baru sebelum menaikkan traffic.

Tips debugging saat masalah muncul

  • Bandingkan artefak mentah: header, cookie, claim token, TTL Redis, dan body respons dari kedua runtime.
  • Periksa sumber waktu: banyak masalah auth ternyata berasal dari clock skew atau parsing timestamp.
  • Cari fallback tersembunyi: parser lama yang masih dipanggil saat verify gagal sering menutupi akar masalah.
  • Verifikasi jalur proxy: header forwarding, HTTPS detection, dan client IP memengaruhi cookie dan rate limit.
  • Uji dengan data unicode dan input batas: mismatch encoding sering tidak muncul pada ASCII biasa.

Penutup

Migrasi backend ke Bun dan Rust bisa dilakukan aman, tetapi audit session dan secret harus menjadi bagian inti dari rencana migrasi, bukan pekerjaan tambahan di akhir. Risiko terbesar biasanya muncul ketika dua runtime memproses auth dan session dengan asumsi yang sedikit berbeda. Fokuskan audit pada kompatibilitas token, invalidasi session, rotasi key, validasi input, cookie flags, logging sensitif, upload abuse, dan rate limit.

Jika Anda hanya mengingat satu hal, ingat ini: selama masa transisi, keputusan keamanan untuk input yang sama harus konsisten di semua runtime. Semua checklist, test staging, dan observabilitas pasca-rilis pada dasarnya bertujuan memastikan prinsip itu benar-benar tercapai.