Security headers and CORS
Security headers are instructions a site sends to the browser about what it is allowed to do with the page. This covers the ones worth having, what their absence permits, and the CORS configuration that quietly turns a read-only API into an account takeover.
Contents
Do missing headers matter on their own?
Rarely. A missing header is almost never the vulnerability. It is the reason some other vulnerability works, or works better than it should. A content security policy does not stop a cross-site scripting bug being introduced, it stops the injected script reaching the attacker's server once it is. Reading these findings as "one more thing to add" misses what they are for: they are the layer that decides how bad the next mistake gets.
Which one is worth doing first?
Strict transport security, because it is one line and it closes an attack that needs no bug on your side at all. Without it, the first request a browser makes to your domain can be plain HTTP, and anything on the path can answer it. Content security policy is the highest value of the rest and also the most work, because a useful policy has to be written against what the site actually loads rather than copied.
Why is a permissive CORS policy treated so much more seriously?
Because it is not a hardening gap, it is a live vulnerability. A response that reflects whatever Origin it was sent and also sets Access-Control-Allow-Credentials: true is telling every browser on the internet that any site may read authenticated responses from your API using your users' cookies. That is account takeover with no phishing step, and it needs nothing from the victim but a visit to the wrong page.
How do I check whether a policy is actually enforcing?
Look at the header name before you read the value. A content security policy sent as Content-Security-Policy-Report-Only enforces nothing at all, which is correct while you are tuning it and useless if it was left that way. The same applies to a policy whose value contains unsafe-inline, which permits the exact injection the policy was added to stop.
Every finding, and what it means
7 checks, each with what it is, why it matters and the fix. Every one has its own link, so a finding can be sent to whoever owns the fix without sending the whole page.
No Strict-Transport-Security header
- What it is
- Your site does not tell browsers to always use HTTPS.
- Why it matters
- Without it, the first request a visitor makes can be plain HTTP, and an attacker on the same network can intercept it before the redirect to HTTPS happens. HSTS closes that window for every visit after the first.
- How to fix it
- Send Strict-Transport-Security: max-age=31536000; includeSubDomains. Start with a shorter max-age if you are not certain every subdomain is HTTPS-ready, because this is hard to walk back.
No Content-Security-Policy header
- What it is
- You are not restricting where the browser may load scripts, styles and other resources from.
- Why it matters
- CSP is the strongest single defence against cross-site scripting. Without it, any injected script runs with the full authority of your page, including access to your users' sessions.
- How to fix it
- Start with Content-Security-Policy-Report-Only so nothing breaks, watch the reports for a week to learn what your site actually loads, then switch to enforcing.
No X-Frame-Options header
- What it is
- Your pages can be embedded in a frame on another website.
- Why it matters
- That enables clickjacking: an attacker overlays an invisible frame of your site on their page so a click that looks harmless actually presses a button in your application, as the logged-in user.
- How to fix it
- Send X-Frame-Options: DENY, or the modern equivalent, a frame-ancestors directive in your Content-Security-Policy.
No X-Content-Type-Options header
- What it is
- Browsers are allowed to guess the type of a response rather than trusting the Content-Type you sent.
- Why it matters
- A file a user uploaded as an image can be sniffed as HTML and executed as a script in your origin. The header turns that guessing off.
- How to fix it
- Send X-Content-Type-Options: nosniff. It is one line and almost never breaks anything.
CORS reflects any origin and allows credentials
- What it is
- Your server echoes back whatever Origin the caller sent and sets Access-Control-Allow-Credentials: true.
- Why it matters
- This is the dangerous combination. Any website a logged-in user visits can make authenticated requests to your API and read the responses, which means reading that user's data from any page on the internet.
- How to fix it
- Replace the reflection with an explicit allowlist of origins you actually trust. Never combine a reflected or wildcard origin with credentials.
CORS wildcard combined with credentials
- What it is
- Your CORS policy allows a wildcard or reflected origin together with credentialed requests.
- Why it matters
- It lets any origin read authenticated responses from your API, which defeats the point of the same-origin policy.
- How to fix it
- List the specific origins that need access. If you need credentials, the origin must be exact.
You publish a security.txt
- What it is
- A security.txt file was found at /.well-known/security.txt.
- Why it matters
- This is a good sign, not a problem. It tells researchers who find a bug in your site exactly where to report it, which is the difference between a private report and a public tweet.
- How to fix it
- Nothing to fix. Do check that the Expires field is in the future and the contact address is still monitored.
Frequently asked
Will adding these headers break my site?
Strict transport security and the two anti-sniffing headers almost never do. A content security policy frequently does on first application, because it is enforcing what the page is genuinely allowed to load and the honest answer is often broader than expected. Deploy that one in report-only mode, read the reports, then enforce.
Does a good grade mean the site is secure?
No, and treating it that way is the main risk of grading headers at all. Every check on this page reads response headers. A site can send a perfect set of them and still have an authorization flaw that hands one user another user's data. Headers are the floor, not the measure.
Should the headers go on the CDN or the application?
Wherever you can guarantee every response gets them, which for most stacks means the edge. The failure mode worth avoiding is a header set in application middleware that error pages and static assets never pass through, so the responses most worth protecting are the ones missing it.
Run this against a domain you own. Start free