Skip to content
PwnDeck logoPwnDeck

Security Headers: The Five That Actually Matter

Most security-header guides list twenty. Here are the five that actually change attacker behaviour, with the misconfigurations I see weekly.

By Javier HernándezPentester @ Accenture | OSCP (in progress)3 min read

Every security-headers cheat sheet on the internet lists twenty headers. In the wild, only five of them actually move the needle on what an attacker can do to your users. The rest are nice-to-haves, deprecated, or papered-over by modern browsers anyway. This is the short list I check first on every engagement.

The five that earn their keep

Strict-Transport-Security. Without HSTS, the first request to your site can be intercepted and downgraded to HTTP, and the attacker keeps the user on a cloned http:// host for the rest of the session. The fix is one line:

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

max-age should be at least one year (31536000). includeSubDomains is what stops the attacker from pivoting to assets.your-domain.com. preload opts you into the Chrome preload list — read the requirements before adding it, because removal takes weeks.

Advertisement

Content-Security-Policy. CSP is the only header on this list that actually mitigates XSS rather than just hardening the boundary. A working policy starts strict and loosens only for the inline scripts you genuinely need, using nonces or hashes — not 'unsafe-inline'. If your CSP contains 'unsafe-inline' and 'unsafe-eval', you do not have a CSP, you have a comment that scanners stop complaining about.

X-Content-Type-Options. One value, no nuance: nosniff. Without it, the browser may guess that your user-uploaded cat.jpg is actually HTML and execute the script tag inside it. There is no reason not to set this.

Referrer-Policy. Default browser behaviour leaks the full referring URL to third-party requests, which means session tokens in URLs, internal ticket IDs, and password-reset links end up in analytics logs you don't control. strict-origin-when-cross-origin is the sane default; no-referrer for high-sensitivity apps.

Cross-Origin-Opener-Policy. The unsung hero. Setting it to same-origin isolates your top-level browsing context so that a window opened by your site can't reach back through window.opener to read your DOM. This kills a whole class of cross-window tabnabbing and Spectre-style cross-origin leaks. Pair it with Cross-Origin-Embedder-Policy: require-corp if you want SharedArrayBuffer access; otherwise COOP alone is the easy win.

Misconfigurations I see weekly

The most common one is CSP with 'unsafe-inline' 'unsafe-eval' *. The team copied a template, the inline scripts broke, somebody added 'unsafe-inline' "temporarily", and the policy has been ceremonial ever since. If you have inline scripts you cannot remove, generate per-request nonces and add them to both the header and the script tags. It is a one-day refactor on most stacks.

The second is HSTS with a tiny max-age like 600. Six hundred seconds is less secure than no HSTS at all, because attackers can simply wait out the window. Set it to a year minimum, or don't ship it.

The third — and this one is silent — is X-Frame-Options: ALLOW-FROM https://example.com. ALLOW-FROM was never widely supported and has been dead in Chrome and Firefox for years. Modern browsers ignore it and your site is happily framed by anyone. Use Content-Security-Policy: frame-ancestors instead, which actually works and accepts multiple origins.

Verify the deployed policy, not the config file

Headers are a deployment-time concern, not a code-time one. CDN strips them, reverse-proxies rewrite them, and the staging environment lies. Always pull the response from the actual production host and inspect what the browser sees — our headers checker will dump them for any URL, including the redirect chain so you catch the HSTS-missing-on-301 case that ruins your day. Pair it with a quick TLS pass through our SSL analyzer and you have a baseline you can hand to the client without writing a report.

Five headers. Twenty minutes of work. The difference between "we have security headers" and "we have security headers that do something" is whether someone has actually read the spec for each one. The rest of the list — X-XSS-Protection, Expect-CT, Feature-Policy — is mostly historical baggage at this point. Spend your hardening budget on the five above and move on.

Share this article:LinkedInX

Related articles