The easiest security win you are not using
HTTP security headers are instructions your server sends to the browser that control how your content is handled. They block clickjacking, prevent XSS exploitation, stop MIME-type confusion attacks, and enforce encrypted connections. They are a defense layer that works even when your application code has vulnerabilities.
Adding security headers takes 15-30 minutes. The protection they provide is disproportionately high for the effort involved. Yet most applications, especially those built with AI coding tools, ship without any security headers configured.
This guide covers every header that matters, explains what each one does in plain language, shows you how to implement them on the most common platforms, and warns you about the mistakes that break things.
The essential headers
Content-Security-Policy (CSP)
What it does: Tells the browser exactly which sources are allowed to load scripts, styles, images, fonts, and other resources on your page. Anything not on the allow-list is blocked.
Why it matters: CSP is the most powerful defense against Cross-Site Scripting (XSS). Even if an attacker manages to inject a <script> tag into your page, the browser will refuse to execute it because the script's source is not on your CSP allow-list.
Implementation:
Start with a restrictive policy and add exceptions as needed:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests
Breaking this down:
| Directive | Value | Meaning |
| --------------------------- | ------------------------ | --------------------------------------------------------------------------- |
| default-src | 'self' | By default, only load resources from your own domain |
| script-src | 'self' | Only run scripts from your domain (no inline scripts, no CDNs unless added) |
| style-src | 'self' 'unsafe-inline' | Styles from your domain plus inline styles (needed for most CSS-in-JS) |
| font-src | 'self' | Fonts from your domain only (add Google Fonts URL if needed) |
| img-src | 'self' data: | Images from your domain and data URIs (for inline SVGs, etc.) |
| connect-src | 'self' | API calls only to your domain (add external API domains as needed) |
| frame-ancestors | 'none' | Your page cannot be embedded in an iframe (prevents clickjacking) |
| object-src | 'none' | No Flash or Java applets (legacy but still important to block) |
| base-uri | 'self' | Prevents attackers from changing the base URL for relative links |
| form-action | 'self' | Forms can only submit to your domain |
| upgrade-insecure-requests | (flag) | Automatically upgrade HTTP requests to HTTPS |
Common additions:
If you use Stripe:
script-src 'self' https://js.stripe.com;
frame-src https://js.stripe.com https://checkout.stripe.com;
connect-src 'self' https://api.stripe.com;
If you use Google Fonts:
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
If you use Google Analytics:
script-src 'self' https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com;
img-src 'self' data: https://www.google-analytics.com;
Common mistakes:
- Using
script-src 'unsafe-inline' 'unsafe-eval'defeats the purpose of CSP entirely - Setting CSP as
Content-Security-Policy-Report-Onlyand forgetting to switch to enforcement - Using
*in any directive (allows everything, provides no protection)
Testing: After deploying CSP, open your browser's developer console. CSP violations appear as errors. Fix each violation by adding the necessary source to the appropriate directive, or by refactoring the code to avoid the violation.
Strict-Transport-Security (HSTS)
What it does: Tells the browser to always use HTTPS for your domain, even if the user types http:// or clicks an HTTP link.
Why it matters: Without HSTS, the first request to your site might be over HTTP before the redirect to HTTPS kicks in. An attacker on the same network (coffee shop Wi-Fi, for example) can intercept that first HTTP request and serve a fake version of your site (SSL stripping attack).
Implementation:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
| Parameter | Meaning |
| ------------------- | ------------------------------------------------------------------------------------- |
| max-age=31536000 | Remember this setting for 1 year (in seconds) |
| includeSubDomains | Apply to all subdomains too |
| preload | Eligible for browser preload lists (Chrome, Firefox ship with a list of HSTS domains) |
Common mistakes:
- Setting
max-ageto a short value (use at least 31536000) - Forgetting
includeSubDomains(attackers can usehttp://sub.yourdomain.comto bypass) - Enabling HSTS before ensuring all subdomains support HTTPS
X-Frame-Options
What it does: Controls whether your page can be embedded in an <iframe>.
Why it matters: Without this, an attacker can create a page that loads your site in a hidden iframe and tricks users into clicking buttons they cannot see (clickjacking). This can lead to account deletion, money transfers, or other destructive actions.
Implementation:
X-Frame-Options: DENY
Options:
DENY— page cannot be framed by any site (recommended for most apps)SAMEORIGIN— page can only be framed by your own domain
Note: frame-ancestors 'none' in CSP does the same thing and is the modern replacement. Use both for backward compatibility.
X-Content-Type-Options
What it does: Prevents the browser from guessing ("sniffing") the content type of a response. The browser must use the Content-Type header you set.
Why it matters: Without this, a browser might interpret a JSON response or uploaded file as HTML and execute JavaScript within it. An attacker uploads a file with a .jpg extension but HTML content, and the browser executes it as a web page.
Implementation:
X-Content-Type-Options: nosniff
This is a single, simple value. Always set it.
Referrer-Policy
What it does: Controls how much URL information is sent in the Referer header when users navigate from your site to other sites.
Why it matters: URLs sometimes contain sensitive information: session tokens in query strings, internal page paths, search queries. The Referer header leaks this to every site your users navigate to.
Implementation:
Referrer-Policy: strict-origin-when-cross-origin
This sends the full URL for same-origin requests (your own site) but only the origin (domain) for cross-origin requests. It is the best balance of functionality and privacy.
Stricter option: no-referrer sends no referrer at all, but this breaks some analytics and affiliate tracking.
Ready to apply the FORGE framework?
VibeSec helps knowledge worker teams redesign their processes using the FORGE framework: Skills, Agents, Guardrails, and Schedule. Security is built in, not bolted on. Map your first process in 10 minutes.
Permissions-Policy
What it does: Disables browser features your application does not use, like geolocation, microphone, camera, and payment APIs.
Why it matters: If an XSS vulnerability allows an attacker to run JavaScript on your page, they could access the user's camera, microphone, or location. Permissions-Policy blocks these APIs entirely if your app does not need them.
Implementation:
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()
Each () means "disabled for all origins." If your app needs geolocation, for example, use geolocation=(self) to allow it only from your origin.
Cross-Origin-Opener-Policy (COOP)
What it does: Isolates your page from other windows/tabs that your page might have opened or been opened by.
Why it matters: This mitigates Spectre-style side-channel attacks that can read data from cross-origin windows. It also prevents other windows from getting a reference to your window object.
Implementation:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy (CORP)
What it does: Controls which origins can include your resources (images, scripts, etc.) in their pages.
Why it matters: Prevents other sites from embedding your resources, which can be used for timing attacks, data exfiltration, or simply hotlinking your assets.
Implementation:
Cross-Origin-Resource-Policy: same-origin
Use same-site if you need resources to be accessible across subdomains. Use cross-origin only for intentionally public resources like CDN assets.
Platform-specific implementation
Cloudflare Pages
Create a public/_headers file:
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=()
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Or use a Cloudflare Pages Function middleware for dynamic header control (recommended for CSP that varies by route).
Vercel
In vercel.json:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{
"key": "Referrer-Policy",
"value": "strict-origin-when-cross-origin"
},
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains; preload"
},
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'"
}
]
}
]
}
Netlify
In public/_headers:
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'
Express / Node.js
Using the helmet package (recommended):
npm install helmet
import helmet from "helmet";
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
connectSrc: ["'self'"],
frameAncestors: ["'none'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
},
},
})
);
Or manually:
app.use((req, res, next) => {
res.setHeader("X-Frame-Options", "DENY");
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
res.setHeader(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains; preload"
);
res.setHeader(
"Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'"
);
next();
});
How to verify your headers
Command line
curl -I https://yourdomain.com
Check that each security header appears in the response.
Online tools
- securityheaders.com — grades your headers A through F
- observatory.mozilla.org — comprehensive security check including headers
- csp-evaluator.withgoogle.com — specifically evaluates your CSP for weaknesses
Browser DevTools
Open DevTools (F12) → Network tab → click on the main document request → check the Response Headers section. CSP violations appear in the Console tab as errors.
Common pitfalls
CSP breaks your app
The most common issue. You deploy a strict CSP and your app stops working because a third-party script, font, or API call is blocked.
Fix: Deploy with Content-Security-Policy-Report-Only first. This logs violations without blocking them. Review the violations in your browser console, add legitimate sources to your policy, then switch to enforcement mode.
Inline scripts and styles
CSP's script-src 'self' blocks inline <script> tags and onclick handlers. Many frameworks use inline styles, which are blocked by style-src 'self'.
Fix for styles: Add 'unsafe-inline' to style-src. This is acceptable because inline style injection is much less dangerous than inline script injection.
Fix for scripts: Refactor inline scripts to external files, or use CSP nonces:
Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">
/* allowed */
</script>
The nonce must be unique per request and cryptographically random.
HSTS on a site without full HTTPS
If any page, subdomain, or asset is served over HTTP, HSTS with includeSubDomains will break it. Ensure everything is HTTPS before enabling HSTS.
Overly permissive headers
Setting headers but making them too permissive:
Content-Security-Policy: default-src *— allows everything, provides zero protectionX-Frame-Options: ALLOW-FROM *— not a valid value (only DENY, SAMEORIGIN, or ALLOW-FROM specific-origin)Access-Control-Allow-Origin: *withAccess-Control-Allow-Credentials: true— browsers reject this combination, but it signals a misunderstanding of CORS
Frequently Asked Questions
What security headers does every web application need?
At minimum, every web application should have these 6 headers: Content-Security-Policy (prevents XSS), Strict-Transport-Security (forces HTTPS), X-Frame-Options (prevents clickjacking), X-Content-Type-Options (prevents MIME sniffing), Referrer-Policy (controls referrer leakage), and Permissions-Policy (restricts browser features). These take 15 minutes to configure and dramatically improve your security posture.
How do I check if my site has security headers?
Run curl -sI https://yoursite.com in your terminal to see all response headers. You can also use securityheaders.com for a visual scan and grade. If you are missing Content-Security-Policy or Strict-Transport-Security, those should be your first priorities.
Why does my AI coding assistant never add security headers?
AI coding tools optimize for functionality — making your application work. Security headers are a defense-in-depth layer that does not affect whether your application functions correctly, so AI tools consistently skip them. In our security reviews, AI-generated applications have zero security headers configured in the vast majority of cases.
What is Content-Security-Policy and why is it the most important header?
Content-Security-Policy (CSP) tells browsers which resources are allowed to load on your page. It is the single most impactful security header because it prevents cross-site scripting (XSS) attacks even when your application code has vulnerabilities. A basic CSP that blocks inline scripts and restricts sources to your own domain stops most XSS attacks.
Can security headers break my application?
Yes, if configured incorrectly. Content-Security-Policy is the most likely to cause issues because it blocks resources that do not match your policy. Use Content-Security-Policy-Report-Only mode first to identify violations without blocking anything, fix the violations, then switch to enforcement mode.
Do I need different headers for different frameworks?
The headers themselves are the same regardless of framework. What differs is how you configure them — Express uses the helmet package, Next.js uses next.config.js headers, Cloudflare uses _headers files, and so on. The implementation examples above cover the most common deployment platforms.
Headers that AI coding tools miss
In our security reviews, AI-generated applications consistently have zero security headers configured. The AI generates working server code that responds to requests, and security headers are not part of making requests work.
This is one of the easiest security improvements you can make to an AI-built application:
- Add the headers using the platform-specific examples above
- Test with
Content-Security-Policy-Report-Onlyfirst - Fix any violations
- Switch to enforcement mode
- Verify with securityheaders.com
Fifteen minutes of work. Massive security improvement. Do it today.
Need help configuring security headers for your specific stack, or want a comprehensive review that goes beyond headers? Contact us. We help teams building with agentic AI ship secure applications.