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
preloadhampir tidak bisa dibatalkan — pahami sebelum memasangnya. - Header yang dipasang dua kali bisa menghasilkan nilai ganda, dan browser memilih yang paling ketat.
Permissions-Policymematikan API yang tidak pernah dipakai portal berita.
Tujuh header
| Header | Nilai | Mencegah |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains | Penurunan ke HTTP |
X-Content-Type-Options | nosniff | Browser menebak tipe konten dan mengeksekusi unggahan |
Referrer-Policy | strict-origin-when-cross-origin | URL lengkap bocor ke situs lain |
X-Frame-Options | DENY | Clickjacking (untuk browser lama) |
Content-Security-Policy | Lihat materi CSP | XSS |
Permissions-Policy | camera=(), microphone=(), geolocation=() | Skrip pihak ketiga meminta izin perangkat |
Cross-Origin-Opener-Policy | same-origin | Tab 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-demand | Ya | Ya |
| Berlaku untuk halaman prerender | Ya | Tidak |
Berlaku untuk aset & public/ | Ya | Tidak |
| Berlaku saat origin mati | Ya | Tidak |
| Bisa dinamis per pembaca | Terbatas | Ya (nonce CSP) |
| Berubah tanpa deploy | Ya | Tidak |
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
| Bagian | Artinya | Risiko |
|---|---|---|
max-age=31536000 | Browser mengingat 1 tahun | Salah setel = situs tidak bisa diakses selama itu bagi yang sudah menerimanya |
includeSubDomains | Berlaku untuk semua subdomain | Subdomain internal yang masih HTTP jadi tidak bisa diakses |
preload | Ditanam di browser sebelum kunjungan pertama | Pencabutannya 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.