← Semua pembelajaran / Astro Nol → Portal Berita
Fase 7 · Keamanan

Header keamanan & tempat memasangnya

Header keamanan murah dan berdampak. Yang tidak jelas adalah di mana memasangnya — dan memasang di dua tempat sekaligus menghasilkan masalah tersendiri.

Intisari

  • Pasang di Cloudflare supaya berlaku untuk semua respons, termasuk aset dan halaman yang di-prerender.
  • Middleware Astro tidak berjalan untuk halaman prerender maupun berkas di public/.
  • HSTS dengan preload hampir tidak bisa dibatalkan — pahami sebelum memasangnya.
  • Header yang dipasang dua kali bisa menghasilkan nilai ganda, dan browser memilih yang paling ketat.
  • Permissions-Policy mematikan API yang tidak pernah dipakai portal berita.

Tujuh header

HeaderNilaiMencegah
Strict-Transport-Securitymax-age=31536000; includeSubDomainsPenurunan ke HTTP
X-Content-Type-OptionsnosniffBrowser menebak tipe konten dan mengeksekusi unggahan
Referrer-Policystrict-origin-when-cross-originURL lengkap bocor ke situs lain
X-Frame-OptionsDENYClickjacking (untuk browser lama)
Content-Security-PolicyLihat materi CSPXSS
Permissions-Policycamera=(), microphone=(), geolocation=()Skrip pihak ketiga meminta izin perangkat
Cross-Origin-Opener-Policysame-originTab lain memanipulasi window portalmu

Permissions-Policy paling relevan untuk portal berita yang menayangkan iklan. Skrip iklan pihak ketiga bisa mencoba mengakses kamera, mikrofon, atau lokasi. Portal berita tidak pernah membutuhkan satu pun dari itu — mematikannya di tingkat header berarti tidak ada skrip mana pun di halamanmu yang bisa memintanya, sekalipun ia mencoba.

Di mana memasangnya

Cloudflare (Transform Rules)Middleware Astro
Berlaku untuk halaman on-demandYaYa
Berlaku untuk halaman prerenderYaTidak
Berlaku untuk aset & public/YaTidak
Berlaku saat origin matiYaTidak
Bisa dinamis per pembacaTerbatasYa (nonce CSP)
Berubah tanpa deployYaTidak

Rekomendasi: header statis di Cloudflare, header dinamis di middleware. Jangan memasang header yang sama di dua tempat — kalau nilainya berbeda, hasilnya bisa dua header dengan nilai berbeda, dan perilakunya bergantung pada browser.

Cloudflare → Rules → Transform Rules → Modify Response Header

Set static:
  Strict-Transport-Security      max-age=31536000; includeSubDomains
  X-Content-Type-Options         nosniff
  Referrer-Policy                strict-origin-when-cross-origin
  X-Frame-Options                DENY
  Permissions-Policy             camera=(), microphone=(), geolocation=(), interest-cohort=()
  Cross-Origin-Opener-Policy     same-origin

Remove:
  Server
  X-Powered-By

HSTS: baca ini sebelum memasang preload

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
BagianArtinyaRisiko
max-age=31536000Browser mengingat 1 tahunSalah setel = situs tidak bisa diakses selama itu bagi yang sudah menerimanya
includeSubDomainsBerlaku untuk semua subdomainSubdomain internal yang masih HTTP jadi tidak bisa diakses
preloadDitanam di browser sebelum kunjungan pertamaPencabutannya butuh berbulan-bulan

Naikkan bertahap: max-age=300, lalu sehari, lalu seminggu, baru setahun. Dan sebelum includeSubDomains, inventarisasi seluruh subdomain portalmu — portal berita berumur hampir selalu punya subdomain yang terlupakan: staging lama, situs event, panel vendor. Kalau salah satunya belum HTTPS, ia akan mati total bagi pembaca yang pernah membuka domain utamamu.

Yang harus dihapus

Server: nginx/1.24.0        ← sebutkan versi = memberi tahu penyerang apa yang harus dicoba
X-Powered-By: PHP/7.4.33    ← ini masih akan muncul dari CI3 selama transisi
X-AspNet-Version: …

Selama Fase 9, sebagian trafikmu masih dilayani CI3 — dan PHP mengirim X-Powered-By lengkap dengan versinya secara bawaan. Kalau versinya sudah tidak didukung, itu undangan. Hapus di Cloudflare supaya berlaku untuk kedua origin sekaligus.

Header pada respons yang bukan HTML

// Middleware: jangan pasang CSP pada JSON atau gambar
const tipe = res.headers.get("content-type") ?? "";

if (tipe.includes("text/html")) {
  res.headers.set("Content-Security-Policy", kebijakan);
}

// Yang ini aman untuk semua tipe
res.headers.set("X-Content-Type-Options", "nosniff");

CSP pada respons JSON tidak berbahaya tapi menambah byte ke tiap respons API — di 1,25 juta request per jam, header yang tidak berguna tetap ada biayanya.

Verifikasi

curl -sI https://portal.contoh.id/ | grep -iE \
  'strict-transport|x-content-type|referrer-policy|x-frame|content-security|permissions-policy'

# Periksa juga halaman prerender dan aset — ini yang sering terlewat
curl -sI https://portal.contoh.id/tentang | grep -ci strict-transport
curl -sI https://portal.contoh.id/_astro/index.abc123.js | grep -ci x-content-type

Latihan: pasang keenam header statis di Cloudflare Transform Rules dengan max-age=300 untuk HSTS. Verifikasi dengan curl -I pada tiga jenis URL: halaman on-demand, halaman prerender, dan berkas aset. Ketiganya harus punya header yang sama. Lalu inventarisasi seluruh subdomain portalmu sebelum memutuskan soal includeSubDomains.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.