Content Security Policy
CSP adalah jaring pengaman terakhir saat XSS lolos. Astro menyediakannya hampir otomatis, dengan empat batasan yang harus kamu ketahui sebelum menyalakannya.
Intisari
security: { csp: true }membuat Astro menghitung hash tiap skrip dan gaya, lalu menuliskannya ke<meta>.- Tidak kompatibel dengan
<ClientRouter />โ kamu harus memilih salah satu. - Tidak bekerja di
astro dev; hanya dibuilddanpreview. - Shiki tidak kompatibel karena memakai gaya sebaris; pakai Prism kalau kamu menyorot kode.
- Skrip pihak ketiga โ iklan, analitik โ butuh
directivesmanual, dan itu bagian tersulitnya di portal berita.
Apa yang dilakukannya
// astro.config.mjs
export default defineConfig({
security: {
csp: {
algorithm: "SHA-256",
directives: [
"default-src 'self'",
"img-src 'self' data: https://cdn.portal.contoh.id",
"font-src 'self'",
"connect-src 'self' https://api.midtrans.com",
"frame-src https://app.midtrans.com",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
"object-src 'none'",
],
},
},
});
Astro memindai tiap halaman saat build, menghitung hash SHA-256 dari setiap blok
<script> dan <style>, lalu menyisipkan:
<meta http-equiv="content-security-policy"
content="script-src 'self' 'sha256-โฆ'; style-src 'self' 'sha256-โฆ'; default-src 'self'; โฆ">
Hasilnya: skrip yang disuntikkan penyerang lewat XSS tidak akan dijalankan browser, karena hash-nya tidak ada di daftar. Ini pertahanan berlapis โ ia tidak menggantikan escaping, ia menangkap yang lolos.
Empat batasan, dan yang paling penting untukmu
| Batasan | Artinya |
|---|---|
Tidak kompatibel dengan <ClientRouter /> | Transisi halaman dari Fase 4 harus dimatikan โ atau CSP dipasang dengan cara lain |
Tidak bekerja di astro dev | Uji di pnpm build && pnpm preview, selalu |
| Shiki memakai gaya sebaris | Ganti ke Prism kalau kamu menyorot kode di artikel |
| Skrip & gaya eksternal | Harus kamu daftarkan sendiri lewat directives |
Baris pertama adalah keputusan yang harus kamu ambil sadar. Di Fase 4 kita membahas
ClientRouter untuk transisi halus antar artikel; di sini kita menemukan bahwa CSP bawaan
Astro tidak bekerja bersamanya. Untuk portal berita berbayar, saranku CSP yang menang โ
transisi halaman adalah kenyamanan, sementara XSS di halaman yang memegang sesi berbayar adalah
pengambilalihan akun. Kalau kamu tetap ingin keduanya, pasang CSP di lapisan Cloudflare atau di
middleware dengan nonce yang kamu kelola sendiri, dan terima bahwa hash otomatis Astro tidak ikut.
CSP lewat middleware, kalau kamu butuh ClientRouter
// src/middleware/keamanan.ts
import { defineMiddleware } from "astro:middleware";
import { randomBytes } from "node:crypto";
export const headerKeamanan = defineMiddleware(async (ctx, next) => {
const nonce = randomBytes(16).toString("base64");
ctx.locals.nonce = nonce;
const res = await next();
// Hanya untuk HTML; jangan pasang di JSON atau gambar.
if (res.headers.get("content-type")?.includes("text/html")) {
res.headers.set(
"Content-Security-Policy",
[
`default-src 'self'`,
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'nonce-${nonce}'`,
`img-src 'self' data: https://cdn.portal.contoh.id`,
`connect-src 'self' https://api.midtrans.com`,
`frame-src https://app.midtrans.com`,
`frame-ancestors 'none'`,
`base-uri 'self'`,
`form-action 'self'`,
`object-src 'none'`,
`upgrade-insecure-requests`,
].join("; "),
);
}
return res;
});
<script nonce={Astro.locals.nonce}>
// hanya skrip dengan nonce yang benar yang dijalankan
</script>
Nonce harus baru tiap request โ dan itu berarti halamannya tidak bisa di-cache di edge. Ini pertukaran yang nyata dan sering tidak disadari: CSP berbasis nonce dan cache CDN saling bertentangan. Untuk portalmu, jalan tengahnya: CSP berbasis hash (statis, cacheable) untuk halaman publik, dan CSP berbasis nonce untuk halaman personal yang memang tidak di-cache.
Direktif yang penting untuk portal berita
| Direktif | Nilai | Yang dicegah |
|---|---|---|
frame-ancestors 'none' | โ | Clickjacking: portalmu di-iframe di situs lain |
base-uri 'self' | โ | Penyerang mengubah <base> lalu membelokkan semua URL relatif |
form-action 'self' | โ | Form login yang dibelokkan ke server penyerang |
object-src 'none' | โ | Plugin lama yang bisa mengeksekusi kode |
frame-src | Domain Midtrans | Snap butuh ini; tanpanya pembayaran tidak muncul |
connect-src | 'self' + API | Data dikirim ke server penyerang lewat fetch |
Bagian tersulit: iklan dan analitik
Skrip iklan memuat skrip lain, yang memuat skrip lain lagi. Mendaftarkan semuanya satu per satu tidak praktis dan akan rusak setiap kali jaringan iklan berubah.
script-src 'self' 'nonce-abc123' 'strict-dynamic'
'strict-dynamic' adalah jawabannya: skrip yang kamu percayai (punya nonce) boleh memuat skrip
lain, dan skrip turunannya mewarisi kepercayaan itu. Kamu cukup memberi nonce ke tag iklan tingkat atas.
Ini pelonggaran nyata, dan harus disadari. 'strict-dynamic' berarti kamu
mempercayai seluruh rantai yang dimuat jaringan iklanmu. Kalau salah satu di antaranya dikompromikan โ
yang pernah terjadi pada beberapa jaringan iklan โ CSP tidak akan menolongmu. Pertukarannya tetap lebih
baik daripada 'unsafe-inline', yang mematikan perlindungan sepenuhnya.
Menyalakannya tanpa merusak apa pun
Content-Security-Policy-Report-Only: default-src 'self'; โฆ; report-uri /api/csp-report
// src/pages/api/csp-report.ts
export const POST: APIRoute = async ({ request }) => {
const laporan = await request.text();
console.warn(JSON.stringify({ level: "warn", csp: laporan.slice(0, 2000) }));
return new Response(null, { status: 204 });
};
Jalankan dalam mode report-only selama satu atau dua minggu, kumpulkan pelanggarannya, perbaiki kebijakannya, baru beralih ke mode penegakan. Menyalakan CSP ketat langsung di produksi portal berita hampir pasti mematikan sesuatu โ biasanya iklan atau embed media sosial โ dan kamu akan mengetahuinya dari keluhan, bukan dari log.
Uji
pnpm build && pnpm preview
curl -sI http://localhost:4321/ | grep -i content-security-policy
curl -s http://localhost:4321/ | grep -o 'http-equiv="content-security-policy"[^>]*' | head -1
Latihan: pasang CSP mode report-only lewat middleware plus endpoint penerima laporan. Muat
beberapa halaman portalmu โ termasuk yang ada iklan dan embed โ dan kumpulkan pelanggarannya selama
sehari. Susun kebijakan yang bersih dari sana. Lalu coba nyalakan security.csp bawaan Astro
bersama <ClientRouter /> dan amati apa yang rusak โ itu batas yang perlu kamu rasakan
sendiri sebelum memilih.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.