This episode covers securing an Astro site: secure headers and Content Security Policy, XSS mitigation and content sanitization, securing API calls and tokens, and strategies for protecting sensitive content on static sites.

A static site has no server-side database, but that does not mean it is immune to security problems. Episode 12 covers the security layers you must install: secure headers, Content Security Policy (CSP), cross-site scripting (XSS) mitigation, and token security.
Many attacks on modern sites do not target the server but the visitors: malicious scripts injected through content or comments, phishing links, or loading unknown resources. The best defense is prevention at the header and sanitization level — not just relying on one framework.
This episode equips you with a security checklist you can apply right after deployment.
Secure headers are HTTP response headers that restrict browser behavior. Install them through your hosting platform's file. Example in vercel.json:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" }
]
}
]
}The X-Content-Type-Options: nosniff header prevents the browser from guessing file types, and Strict-Transport-Security enforces HTTPS connections. All three close common gaps without code changes.
CSP controls which sources of scripts, styles, and images are allowed to load. It is the front-line defense against XSS. Example of a simple CSP:
default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'The script-src 'self' policy only allows scripts from your own domain — scripts from other sources are blocked by the browser. Other domains are added explicitly:
script-src 'self' https://cdn.contoh.dev; img-src 'self' data:When adding a CDN or analytics domain, register it in the CSP. Any violation will show up in the browser console — start from a strict policy and loosen it only when necessary.
One of Astro's strengths: all output is escaped automatically. Values like {variabel} are rendered as plain text, not HTML. This blocks script injection through untrusted data.
Content that comes from a CMS or user input must be sanitized before rendering. Astro provides the render() helper for content collection content that is already safe. For raw HTML from other sources, use a sanitization library:
import sanitizeHtml from "sanitize-html";
const htmlAman = sanitizeHtml(kontenDariCms, {
allowedTags: ["b", "i", "em", "strong", "a"],
allowedAttributes: { a: ["href", "target"] },
});The sanitizeHtml(konten, { allowedTags }) function strips all disallowed tags. With a whitelist like the one above, only basic formatting and links pass through.
Tokens and API keys must not end up in the client bundle. Remember the rule from episode 8: only PUBLIC_ variables are sent to the browser. Secret tokens are used at build time or in server endpoints:
const res = await fetch("https://api.contoh.dev/data", {
headers: { Authorization: `Bearer ${import.meta.env.API_TOKEN}` },
});import.meta.env.API_TOKEN only exists in the build process or the server runtime — it is never exposed in the HTML. Make sure that variable is set in the CI/CD environment, not committed.
Sensitive content — such as an internal dashboard — must not be rendered statically. Anyone who knows the URL can read it. The right solution: render that page on the server with authentication (episode 13), or move it to a separate application. For static sites, "hiding behind an obscure URL" is not security.
Warning
CSP is not a substitute for content sanitization, and sanitization is not a substitute for CSP. Install both: sanitization for incoming data, CSP as a safety net when something slips through.
Before deploying to production, run this checklist:
curl -sI https://situs-kalian.dev | grep -i -E "x-content-type-options|strict-transport|content-security"The curl -sI command fetches the response headers and filters the security-related ones. If the line is empty, the header is not installed.
Once the checklist passes, make checking part of your routine: verify headers on every deploy, review the CSP whenever you add a third-party script, and always sanitize input. Security is not a one-time step but a continuous process.
Episode 12 arms you with security practices: secure headers and CSP, XSS mitigation through default escaping and content sanitization, protecting tokens so they never leak to the client, and the awareness that sensitive content needs server authentication, not a secret URL.
The key takeaways:
curl -sI after every deployment.In the next episode 13, we will cover authentication and protected content: authentication patterns for static sites, client-side auth with astro-auth or external providers, protecting routes and gated content, and session management with secure cookies. Your sensitive content will have a secure entry point.