// 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)