What curl -I Actually Showed

The assumption going in was that this site's headers were already in reasonable shape - the edge-level security headers had been set up early, and nothing had thrown an obvious error since. That assumption lasted about as long as it took to run a plain response-header check against a handful of live pages and read the output line by line instead of skimming it.

Nothing was broken in the sense of a page failing to load. Everything rendered, every script fired, every form submitted. The gaps were the kind that don't show up as errors at all - a content policy that was technically present but permissive enough to not be doing much work, a referrer policy left to whatever the browser defaulted to, and a caching rule written for one kind of file being quietly applied to a completely different kind of file.

None of this shows up in a screenshot. It only shows up when someone reads the response headers as a set of enforced rules, not as background noise the browser handles for you.

Before and After

Header Before After
Content-Security-Policy No explicit script or connect allowlist - fell back to broad defaults that didn't reflect what was actually loading Named allowlist covering exactly the origins in active use - nothing more
Referrer-Policy Not set - left to browser default Explicitly set to strict-origin-when-cross-origin
X-Content-Type-Options Not set nosniff
Permissions-Policy Not set Camera, microphone, and geolocation explicitly denied
Cache-Control - HTML documents Same long-lived edge TTL as static assets Short TTL, revalidated on each request
Cache-Control - static assets Same rule as HTML documents Long TTL, marked immutable - filenames don't change without a deploy
style-src 'unsafe-inline' Present Present - kept on purpose

Decision Log

Tightening the content policy to a named allowlist

The site only talks to a small, known set of external origins - a tag manager, an analytics endpoint, a form handler. A policy that doesn't name them explicitly isn't protecting against anything specific; it's just present. Rewriting it as an exact allowlist meant the policy now reflects reality instead of a template default, and anything unexpected trying to load from somewhere else gets blocked rather than waved through.

Setting a referrer policy instead of inheriting one

Leaving this unset means every browser picks its own default, and those defaults haven't been consistent across versions. Setting it explicitly means the full page URL - including anything sensitive in a query string - doesn't leak to third-party origins on outbound requests, regardless of what browser someone's using.

Splitting the cache rule for HTML from static assets

This was the one that actually mattered for day-to-day work, not just security posture. HTML documents and static assets were sharing one caching rule, which meant a page edit could sit stale at the edge for as long as a fingerprinted CSS file was supposed to. Splitting them means content updates show up promptly, and asset caching stays aggressive where it's actually safe to be aggressive.

Leaving style-src 'unsafe-inline' alone

This is the one that stayed. Every page on this site scopes its own layout tweaks in a page-level <style> block rather than a shared stylesheet, by design - no build step means no easy way to generate per-page nonces or hashes for a static file tree. Removing this directive would mean restructuring how every page is styled, for a category of risk that's already low compared to what an unrestricted script policy would have allowed. Not every gap is worth closing the same day it's found.

Frequently asked questions

Why do response headers matter if the page already loads fine?

A page that renders correctly and a page that's actually enforcing its own security and caching rules are two different questions. Headers are instructions the browser follows whether or not anything looks different on screen.

Won't a strict content policy break analytics or form tools?

Only if it's written generically. A policy built from the actual list of origins in use is exact, not a guess - it allows what's really there and nothing else.

Why keep any "unsafe" rule in place instead of removing it outright?

Because every removal has a cost on the other side. This one would have meant reworking the entire page-styling approach for a marginal reduction in a risk category that was already low. Judgment call, documented, not an oversight.

How often does this need to be checked again?

Any time a new third-party origin gets added - a form service, an analytics tool, an embed. That's the exact moment a hand-written allowlist quietly goes stale.

A header audit doesn't take long - not having one costs more

If response headers haven't been checked since launch, they're probably making decisions on your behalf that nobody actually made.