A handful of response headers block whole classes of attacks like protocol downgrades, XSS and clickjacking.
Beyond TLS itself, several response headers tell browsers to enforce stricter rules.
Strict-Transport-Security (HSTS): max-age=63072000; includeSubDomains; preload tells the browser to use HTTPS only for this domain for two years, even if a user types http://. That prevents downgrade attacks on public Wi-Fi. Make sure every subdomain supports HTTPS first; it's hard to undo.
Content-Security-Policy (CSP) restricts where scripts, styles, images and connections may come from, which limits the damage of an XSS bug. A strict policy uses nonces: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none', with the same random nonce on each allowed <script>. Start with Content-Security-Policy-Report-Only to find violations without breaking the page.
Clickjacking: frame-ancestors 'none' in CSP (or the older X-Frame-Options: DENY) stops other sites from embedding yours in an invisible iframe.
Others:
X-Content-Type-Options: nosniff: never guess content types.Referrer-Policy: strict-origin-when-cross-origin(the browser default): don't leak full URLs to other sites.Permissions-Policy: camera=(), geolocation=(): disable features you don't use.Cross-Origin-Opener-Policy: same-origin: isolates your window from cross-origin popups.
Combine these with cookies set Secure; HttpOnly; SameSite. Check a site's headers with the browser devtools, or scanners such as Mozilla's HTTP Observatory.
Example
import http from "node:http";
import { randomBytes } from "node:crypto";
http.createServer((req, res) => {
const nonce = randomBytes(16).toString("base64");
res.setHeader("Strict-Transport-Security", "max-age=63072000; includeSubDomains");
res.setHeader("Content-Security-Policy",
`script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; ` +
`base-uri 'none'; frame-ancestors 'none'`);
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(`<!doctype html><title>Safe</title>
<script nonce="${nonce}">console.log("allowed")</script>
<script>console.log("blocked by CSP")</script>`);
}).listen(8080);Remember
- HSTS forces HTTPS for future visits
- CSP with nonces limits what scripts can run, containing XSS
- frame-ancestors 'none' prevents clickjacking
- Roll out CSP in Report-Only mode first
Watch out for
- Enabling HSTS with includeSubDomains before every subdomain supports HTTPS.