Lewati ke konten utama

Dashboard API — Keamanan & Hardening

Semua item di bawah telah diimplementasikan dan diverifikasi per audit keamanan 2026-05-29–2026-06-05.


Keamanan & Hardening

Auth & Authorization

  • Image proxy hanya mengizinkan CDN domain yang disetujui: allowedProxyHosts dikonfigurasi dengan daftar putih CDN domain resmi — semua IP internal dan alamat jaringan pribadi dihapus dari daftar yang diizinkan. Endpoint proxy juga dilindungi oleh withDashboardAuth sehingga tidak bisa diakses tanpa sesi yang valid.
  • IP allowlist PSP tidak bisa dibypass via header klien: pspClientIP() membaca IP hanya dari r.RemoteAddr atau X-Real-IP yang di-set oleh nginx — header X-Forwarded-For yang berasal dari klien diabaikan sehingga serangan bypass allowlist via header palsu tidak mungkin dilakukan.
  • PSP secret tanpa enkripsi ditolak keras saat startup: decryptSecret() menolak semua nilai yang tidak diawali prefix enc:v1:. Selain itu, probe scan dijalankan saat NewServer dipanggil — jika ada PSP key yang tersimpan dalam plaintext, server langsung panic/log.Fatal dan tidak mau berjalan. Key yang valid secara kriptografis adalah syarat mutlak untuk startup.
  • Master encryption key divalidasi sekali saat startup, bukan per-request: loadMasterKey() dipanggil di NewServer dan akan panic/log.Fatal jika key tidak valid atau tidak ada — tidak ada window di mana server berjalan dengan key yang belum tervalidasi.

Role-Based Access Control

  • Dev tools dinonaktifkan total di production: ENABLE_DEV_TOOLS=false di .env.production memastikan semua 7 endpoint /dev/* tidak didaftarkan ke router sama sekali saat production build. Selain itu, route tersebut hanya diregistrasikan ketika APP_ENV != production atau flag eksplisit ENABLE_DEV_TOOLS=true diset.
  • Bulk email dev endpoint dibatasi ketat ke superadmin: handleDashboardDevEmailBulkExecute menggunakan pemeriksaan hasAnyRole('superadmin') secara eksplisit — meskipun reportsPlanningRoles di masa depan diperluas, role lain tidak akan mendapatkan akses ke endpoint ini karena guard hard-coded ke superadmin.
  • Privilege escalation vertikal dari admin ke superadmin dicegah: assignUserRoleHandler memiliki guard eksplisit yang memeriksa bahwa hanya superadmin yang dapat mengassign role superadmin ke pengguna lain — admin tidak bisa mempromosikan dirinya sendiri atau orang lain ke level tertinggi.
  • Data KYC dan PII merchant hanya bisa diakses role berwenang: handleMerchantRegistrationDetail dan getMerchantKYCPhotosHandler dibatasi ke merchantWriteRoles (superadmin, admin, operations) — role dengan akses terbatas seperti viewer atau partner_admin tidak bisa melihat foto KYC, NPWP, atau data identitas sensitif merchant.
  • Seluruh mutation KYC dilindungi role gate: Semua 8 mutation handler untuk approve/reject/update KYC merchant memiliki pemeriksaan merchantWriteRoles atau kombinasi superadmin+admin+operations — tindakan yang berdampak pada status kepatuhan merchant tidak bisa dieksekusi oleh semua authenticated dashboard user.
  • Enumerasi nomor HP staf merchant dibatasi: listMerchantStaffHandler dibatasi ke superadmin, admin, operations, dan support — role viewer atau partner_admin tidak bisa melakukan enumerasi data kontak staf merchant.
  • Daftar merchant dengan data finansial sensitif terlindungi: listMerchantsHandler dibatasi ke merchantWriteRoles — daftar merchant beserta data finansial (MDR, NMID, MID, TID, PSP ID) tidak terekspos ke semua session dashboard yang terautentikasi.
  • Detail merchant penuh hanya untuk role berwenang: getMerchantHandler menggunakan gate profil + merchantWriteRoles — detail NMID, MID, TID, PSP ID, NPWP, MDR tidak bisa dibaca oleh semua role dashboard.
  • Inventori device hanya bisa dilihat role berwenang: handleDashboardDevices, listDeviceModelsHandler, dan getDeviceModelHandler semuanya dibatasi ke deviceWriteRoles atau deviceModelWriteRoles — daftar device inventory dan sinyal harga model device tidak terekspos ke semua session dashboard.
  • Serial number dan SIM card terminal terlindungi: listTerminalStockHandler dan handleDashboardTerminalStockUnits dibatasi ke deviceWriteRoles — data sensitif hardware seperti serial number dan nomor SIM card tidak bisa diakses sembarangan.
  • Enumerasi terminal payment merchant dibatasi: listMerchantDevicesHandler menggunakan gate profil + deviceWriteRoles — semua session dashboard tidak bisa melakukan enumerasi terminal payment milik merchant.
  • Struktur pricing dan commission rate partner terlindungi: listCommercialPlans dan listRevenueShareRules pada cabang GET dibatasi ke partnerWriteRoles — detail pricing tier, split percentage, dan commission rate tidak terekspos ke semua role.
  • Credentials partner hanya bisa dienumerasi role berwenang: handleDashboardPartnerCredentialsList pada GET memiliki hasAnyRole(partnerWriteRoles) — viewer dan support tidak bisa melihat daftar credentials partner.

SSRF & Path Traversal Prevention

  • Path traversal pada tester path dicegah: Fungsi allowlist tester (mis. notifTesterPathAllowed dan partnerTesterPathAllowed di dashboard_api) hanya mengizinkan path yang exact-match atau prefix-match daftar yang disetujui, sehingga manipulasi path ke endpoint internal di luar yang diizinkan tidak berhasil. Guard PSP tester pspTesterPathAllowed (yang menolak path mengandung .. sebelum prefix matching) kini berada di integration_api setelah PSP tester di-cutover dari dashboard_api.
  • SSRF dari partner webhook dicegah dengan validasi host resolusi DNS: validateWebhookURL melakukan net.LookupHost untuk semua IP yang ter-resolve dari hostname yang diberikan, kemudian menolak loopback (127.x), alamat private (RFC 1918), dan link-local — URL webhook yang dikirim partner tidak bisa diarahkan ke layanan internal.

Rate Limiting

  • Endpoint payment event dilindungi rate limiter berbasis DB: pspRateLimiter dan defaultPSPRateLimiter menegakkan batas request yang dikonfigurasi per key_id dari database — endpoint notifikasi pembayaran tidak bisa di-flood tanpa batas oleh PSP atau pihak yang menyalahgunakan key.

Data Integrity & Audit Trail

  • Audit trail immutable dengan actor context: insertAuditEvent menggunakan set_config LOCAL untuk menyetel app.actor_user_id dan app.actor_role di level transaksi database. Trigger fn_audit_events_immutable mencegah UPDATE dan DELETE pada tabel audit. Fungsi fn_status_change_audit_fallback bertindak sebagai safety-net untuk perubahan status yang tidak melalui path normal. Data actor_email hanya diisi untuk role superadmin, admin, dan owner.
  • Kegagalan commit audit trail tercatat via slog: Blok audit best-effort di dashboard_shipping_orders.go (4 state transition: packed, dispatched, delivered, returned) menggunakan if commitErr := tx.Commit(); commitErr != nil { slog.Warn(...) } — kegagalan commit tidak lagi ditelan diam-diam (_ = tx.Commit()), sehingga ada observability saat audit trail gagal disimpan. Diimplementasikan 2026-06-13.
  • Body request di endpoint proxy dibatasi ukurannya: forwardCoreAPI menggunakan http.MaxBytesReader dengan batas 2 MB sebelum io.ReadAll — serangan DoS amplification melalui pengiriman body besar ke dashboard_api dicegah di lapisan middleware.
  • Global body size cap 1 MB untuk semua JSON endpoint: Middleware withBodyLimit di server.go membatasi semua POST/PUT/PATCH request ke 1 MB. Request multipart/form-data dikecualikan agar upload handler tetap bisa set limit sendiri (5 MB untuk foto/logo/KYC). Menutup M-176 dan 36 handler JSON lain yang sebelumnya unprotected. Diimplementasikan 2026-06-11.

Secret & Configuration Management

  • MinIO credentials tidak memiliki fallback hardcoded: MINIO_ACCESS_KEY dan MINIO_SECRET_KEY di config.go tidak memiliki nilai default fallback — jika kosong di environment non-development, startup langsung gagal (fail-fast assertion) sehingga tidak ada credential dummy yang secara tidak sengaja digunakan di production.
  • CLI enkripsi PSP membaca secret dari stdin atau file: Tool psp-encrypt menerima plaintext secret via stdin atau path file, bukan via argumen command line — secret tidak pernah muncul di output ps aux atau log shell history yang bisa dilihat proses lain di sistem.
  • Kode acak kriptografis selalu diverifikasi: randomSixteenDigits dan randomEightDigits mengembalikan error jika crypto/rand gagal — caller tidak pernah menerima kode all-zero yang mudah ditebak sebagai fallback diam-diam.

Email Security

  • Header injection via CRLF pada email massal dicegah: buildBulkEmailMessage menggunakan sanitizeHeader yang menghapus karakter \r dan \n dari field From, To, dan Subject — injeksi header email melalui manipulasi CRLF pada input user tidak mungkin dilakukan.