When I took over Fast Pen Tests in September, one of the first things I did was run our free security header scan against our own website. It seemed like the obvious place to start.
It got a D.
A pen testing company with a D on its own scanner. The cobbler's kids have no shoes, etc. I'd rather tell you about it than have you find it yourself, so here's what was wrong and what it took to fix.
What the scan found
The site was missing four of the headers we tell every customer to set:
- Strict-Transport-Security (HSTS). Without it, a browser will happily make a plain HTTP request first, and that first request can be intercepted on a sketchy network.
- X-Content-Type-Options. Without
nosniff, browsers can guess a file's type and end up running something as a script that was never meant to be one. - X-Frame-Options. Without it, anyone can load your site in an invisible frame on their own page and trick people into clicking things. That's clickjacking.
- Content-Security-Policy (CSP). This one tells the browser which sources are allowed to run scripts, load styles, and so on. It's the main thing standing between a small XSS bug and a real problem.
None of these are exotic. They're one line each in a config file. They just never got added, which is how it goes on most sites. The site worked fine, nobody complained, so nobody looked.
The fix
The site runs on Cloudflare Pages. Static files get their headers from a _headers file, and pages rendered by server code need the same headers set in code. I added both, so every response gets the same set no matter where it comes from. If you only set headers in one place, you end up with half your site protected and a scanner that tells you everything's fine because it happened to check the good half.
HSTS, nosniff, and X-Frame-Options: DENY took about five minutes. I also added a Referrer-Policy and a Permissions-Policy while I was in there, since they're just as easy.
CSP took the rest of the afternoon.
Why CSP is the annoying one
A strict CSP says "only run scripts from these places." The problem is that a real marketing site loads scripts from a lot of places. Ours needs Google Tag Manager, Google Analytics, Google Ads, the Meta pixel, Stripe for checkout, Cloudflare Turnstile for the scan form, and Google Fonts. Every one of those has to be allowed explicitly, and some of them load more scripts from other domains once they start.
The way to do it without breaking your site is:
- Write the policy with everything you think you need.
- Ship it as
Content-Security-Policy-Report-Onlyfirst. The browser logs what it would have blocked but doesn't block anything. - Click through every page and every flow (checkout, forms, whatever) and watch the console.
- Add what's missing, then switch it to the enforcing header.
I'll be honest about one tradeoff. Our policy still allows 'unsafe-inline' for scripts, because tag managers inject inline scripts and there's no clean way around that without moving to nonces. That's a known gap and it's on my list. A CSP with one loose rule is still a lot better than no CSP, but I'm not going to pretend it's perfect.
Where it landed
The site now scores an A on the same scan. HTTPS redirect, TLS, HSTS, nosniff, frame protection, CSP, Permissions-Policy, no server version leaking, no X-Powered-By. Ten out of ten checks passing.
Total time was one afternoon, and most of that was CSP.
Why I'm telling you this
Two reasons.
First, if a security company can ship a site with a D, yours might have one too. It's not a character flaw. It's just what happens when something works and nobody's job is to go back and check it.
Second, headers are the cheapest security work there is. They won't stop a determined attacker on their own, but they close off a whole category of easy attacks, and they're the first thing anyone looks at when they size up your site. That includes the security reviewer at the company you're trying to sell to.
Run the free scan on your own domain. It takes about ten seconds. If you get a D, now you know you're in good company.