// keamanan

Cara Situs Ini Diamankan

Terakhir diperbarui: 18 Agustus 2026

1. Arsitektur & alur data

  • $Browser → Cloudflare Workers (Astro SSR) → Supabase (Postgres + Auth + RLS) + Cloudflare KV
  • $Rendering campuran: halaman statis (prerender, via Static Assets) & halaman dinamis (SSR) dalam satu deployment
  • $KV dipakai untuk sesi login & rate limiting; bucket penyimpanan sertifikat bersifat privat
  • $Transport TLS + HSTS preload di semua lapisan; domain berada di belakang edge Cloudflare

2. Security headers (9 aktif)

  • $Content-Security-Policy — default-src 'self'; tanpa 'unsafe-inline'/'unsafe-eval'; frame-ancestors 'none'; upgrade-insecure-requests
  • $Strict-Transport-Security — max-age 2 tahun; includeSubDomains; preload
  • $X-Frame-Options: DENY
  • $X-Content-Type-Options: nosniff
  • $Referrer-Policy: strict-origin-when-cross-origin
  • $Permissions-Policy — kamera, mikrofon, geolokasi, payment dimatikan
  • $Cross-Origin-Opener-Policy: same-origin — anti tab-nabbing
  • $Cross-Origin-Resource-Policy: same-origin
  • $Origin-Agent-Cluster: ?1 — isolasi proses
  • $Diterapkan dua lapis: middleware Worker (response SSR) + _headers (aset statis) — keduanya dijaga sinkron

3. Autentikasi & otorisasi

  • $Login PKCE; MFA TOTP WAJIB untuk admin/editor — ditegakkan di level DATABASE via policy RLS (jwt aal2), bukan hanya UI
  • $RBAC tiga peran (admin/editor/viewer) dengan deny-by-default di dua lapis: aplikasi & Postgres
  • $Rate limit berlapis: per-IP dan per-IP+email; lockout progresif 15 menit → 4 jam; endpoint MFA: 10 percobaan per 10 menit
  • $Mode fail-closed saat KV tidak tersedia: login ditolak (keputusan sadar — lihat §5)
  • $Anti-enumerasi akun: pesan error generik, alamat email tidak dicatat pada blokade

4. Perlindungan data

  • $RLS deny-by-default di semua tabel; bucket penyimpanan sertifikat privat (tidak ada file yang disajikan publik)
  • $Backup 3-2-1 otomatis tiap minggu: 2 salinan off-site (GitHub artifact + Google Drive), dienkripsi GPG AES-256, kunci terpisah
  • $Retensi: pesan kontak 12 bulan; log audit 90 hari; salinan Drive 12 minggu (rotasi otomatis)
  • $RTO ≤ 1 hari, RPO ≤ 7 hari — runbook & drill restore terdokumentasi

5. Keputusan desain (dan trade-off-nya)

  • $Fail-closed rate limiting: jika KV down, login ditolak — risiko Self-DoS diterima secara sadar karena halaman publik tetap melayani (fail-open) dan Dashboard Supabase tetap menjadi jalur darurat
  • $MFA di level DB, bukan cuma UI: kredensial yang bocor tetap tidak berguna meski kode aplikasi di-bypass
  • $Nol pelacakan pengunjung: tanpa analitik pihak ketiga — privasi sebagai default, bukan fitur
  • $Tanpa COEP/SRI: COEP akan memblokir resource eksternal tanpa CORP (Turnstile, supabase.co); Turnstile tidak menyediakan hash SRI yang stabil — risiko lebih besar daripada manfaat untuk skala ini

6. Threat model ringkas (STRIDE)

  • $Spoofing → PKCE + MFA TOTP (aal2) + RBAC deny-by-default
  • $Tampering → HSTS preload, CSP ketat, CORP same-origin, backup terenkripsi GPG
  • $Repudiation → audit log login & akses konfigurasi backup (retensi 90 hari)
  • $Information disclosure → RLS deny-by-default, bucket privat, Referrer-Policy ketat
  • $Denial of service → rate limit fail-closed, lockout progresif, Cloudflare Turnstile pada form publik
  • $Elevation of privilege → role check dua lapis (aplikasi + policy RLS aal2)

7. Responsible disclosure

  • $security.txt (RFC 9116): https://portofolio.indyadirak.my.id/.well-known/security.txt
  • $Laporkan kerentanan: https://github.com/indyadirak/website-portofolio/security/advisories/new
  • $Kebijakan keamanan (SECURITY.md): https://github.com/indyadirak/website-portofolio/blob/main/SECURITY.md
  • $Dokumentasi audit & pemetaan kepatuhan tersedia publik di repositori (docs/SECURITY_COMPLIANCE_MAPPING.md)