Common Security Header Mistakes That Weaken Your Protection
The most damaging security header misconfigurations are not missing headers but headers that look present while doing nothing: a Content-Security-Policy that allows 'unsafe-inline', a CORS policy that reflects any origin alongside credentials, an HSTS header with a max-age of seconds, a deprecated X-Frame-Options: ALLOW-FROM, an over-broad Permissions-Policy, and headers applied to only some responses. Each passes a casual glance yet leaves the underlying vulnerability wide open, creating false confidence in protection that does not actually exist.
That is the summary an AI overview can give you. What it cannot give you is a ranked, fix-in-hand view of which mistakes cost you the most and the exact directive that closes each gap. This post covers the misconfigurations that matter, ordered by real-world impact, then walks each one in detail. It pairs with our companion explainer on what HTTP security headers are — read that first if you need the fundamentals; read this to avoid getting them wrong.
The Misconfigurations That Matter, Ranked
| # | Misconfiguration | Severity | What goes wrong | The fix |
|---|---|---|---|---|
| 1 | Reflected/wildcard CORS with credentials | Critical | Access-Control-Allow-Origin echoes any origin while Allow-Credentials: true lets any site read logged-in responses | Validate Origin against an allowlist; never reflect it blindly; never pair * with credentials |
| 2 | 'unsafe-inline' / 'unsafe-eval' in CSP script-src | Critical | Injected inline scripts execute — CSP stops blocking XSS, its main job | Use nonces ('nonce-…') or hashes ('sha256-…'); remove unsafe-* |
| 3 | Missing HSTS or short max-age | High | max-age=300 is forgotten in minutes, reopening the HTTPS-downgrade / MITM window | max-age=31536000; includeSubDomains; add preload only after months of testing |
| 4 | X-Frame-Options: ALLOW-FROM | High | Ignored by all modern browsers → zero clickjacking protection | DENY/SAMEORIGIN + CSP frame-ancestors for the modern, multi-origin case |
| 5 | Over-permissive or absent Permissions-Policy | Medium | Camera, mic, geolocation, and payment APIs stay available to any embedded script | Deny what you don't use: camera=(), microphone=(), geolocation=() |
| 6 | Header on only some responses | Medium | Errors, redirects, and static assets ship unprotected while the home page passes an audit | Apply headers globally with always (Apache) / edge middleware; verify on 404/500 |
| 7 | Wildcard or unsafe CSP default-src * | High | Allows every origin — equivalent to no CSP | Start at default-src 'self', add specific trusted origins only |
| 8 | Syntax errors / typos (defualt-src) | Medium | Malformed headers are silently ignored — false confidence | Automated validation in CI; scan the live response |
<rect x="24" y="118" width="592" height="34" rx="6" fill="#ffffff" stroke="#fde68a"/>
<circle cx="46" cy="135" r="9" fill="#f59e0b"><animate attributeName="opacity" values="1;0.35;1" dur="1.6s" begin="0.3s" repeatCount="indefinite"/></circle>
<text x="42" y="139" fill="#ffffff" font-size="13" font-weight="700">!</text>
<text x="66" y="139" font-family="ui-monospace, monospace">Content-Security-Policy: default-src *</text>
<rect x="24" y="160" width="592" height="34" rx="6" fill="#ffffff" stroke="#fde68a"/>
<circle cx="46" cy="177" r="9" fill="#f59e0b"><animate attributeName="opacity" values="1;0.35;1" dur="1.6s" begin="0.6s" repeatCount="indefinite"/></circle>
<text x="42" y="181" fill="#ffffff" font-size="13" font-weight="700">!</text>
<text x="66" y="181" font-family="ui-monospace, monospace">Access-Control-Allow-Origin: (reflected) + credentials</text>
<rect x="24" y="202" width="592" height="34" rx="6" fill="#ffffff" stroke="#bbf7d0"/>
<circle cx="46" cy="219" r="9" fill="#16a34a"/>
<path d="M42 219 l3 3 l6 -7" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<text x="66" y="223" font-family="ui-monospace, monospace">X-Frame-Options: SAMEORIGIN</text>
<rect x="24" y="244" width="592" height="34" rx="6" fill="#ffffff" stroke="#bbf7d0"/>
<circle cx="46" cy="261" r="9" fill="#16a34a"/>
<path d="M42 261 l3 3 l6 -7" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<text x="66" y="265" font-family="ui-monospace, monospace">Referrer-Policy: strict-origin-when-cross-origin</text>
Even well-intentioned organizations implement security headers incorrectly, creating false confidence in security that doesn't actually exist. Understanding common misconfiguration patterns helps you avoid the most dangerous mistakes. These errors range from trivial formatting issues to fundamental misunderstandings of what headers actually protect against.
Mistake #1: Overly Permissive Content Security Policy
The most common CSP mistake is making it so permissive that it provides no actual protection. Examples of dangerously weak CSP configurations:
Content-Security-Policy: default-src *
This allows scripts and resources from anywhere, defeating the entire purpose of CSP. It's equivalent to having no CSP at all.
Content-Security-Policy: script-src 'unsafe-inline' *
Allowing unsafe-inline makes CSP ineffective against inline script injection attacks, which are among the most common XSS vectors.
Content-Security-Policy: default-src 'self' *
While less obviously broken than the above, allowing * alongside 'self' negates any protection benefit.
How to avoid it: Start with default-src 'self' and explicitly allow only necessary resources from specific trusted origins. Never use wildcards in CSP unless absolutely necessary, and never combine them with unsafe directives. Use report-only mode to test CSP before deployment so you understand the impact.
If you need to allow inline scripts (which is generally discouraged), use nonces or hashes rather than 'unsafe-inline':
Content-Security-Policy: script-src 'self' 'nonce-abc123def456'
Mistake #1b: Wildcard CORS Combined With Credentials
This is the single highest-impact header mistake, because it hands authenticated data to any website. It hides behind a browser rule that developers work around the wrong way.
You cannot combine a wildcard origin with credentials — the browser refuses to expose the response:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Because that combination is blocked, developers commonly reflect the incoming Origin header straight back to satisfy the browser:
# Attacker sends: Origin: https://evil.example
# Server echoes:
Access-Control-Allow-Origin: https://evil.example
Access-Control-Allow-Credentials: true
This is functionally a credentialed wildcard. Any origin the victim's browser sends — including an attacker's site — is approved, so a logged-in victim visiting evil.example lets that page read their authenticated API responses (profile data, tokens, account details). This is a Cross-Site Request Forgery-adjacent data-exfiltration bug and one of the most frequently exploited CORS misconfigurations.
How to avoid it: Maintain an explicit allowlist of trusted origins. Compare the request's Origin against that list and only echo it back when it matches. Never reflect arbitrary origins, and never send Access-Control-Allow-Credentials: true for public, unauthenticated endpoints. See our CORS glossary entry for the full request/response flow.
Mistake #2: Missing SameSite Attributes on Cookies
Cookies without proper SameSite attributes remain vulnerable to Cross-Site Request Forgery (CSRF) attacks:
Set-Cookie: sessionid=abc123
This cookie is sent with cross-site requests, allowing attackers to forge requests on behalf of users.
How to avoid it: Always specify a SameSite attribute on sensitive cookies:
Set-Cookie: sessionid=abc123; SameSite=Strict
Use SameSite=Strict for session and authentication cookies. If you need cookies to work across some cross-site contexts, use SameSite=Lax as a minimum.
Mistake #3: HSTS Configuration Errors
Several HSTS mistakes are common:
Forgetting includeSubDomains:
Strict-Transport-Security: max-age=31536000
This protects only the main domain, leaving subdomains vulnerable to MITM attacks.
Setting max-age too short:
Strict-Transport-Security: max-age=300; includeSubDomains
A 5-minute expiration means browsers frequently discard the HSTS policy, reducing protection. The standard is 1 year (31536000 seconds) or longer.
Implementing HSTS before fully committing to HTTPS:
HSTS causes browsers to reject all HTTP connections. If you implement it before ensuring all resources load over HTTPS, users will experience broken pages.
How to avoid it: Use max-age=31536000; includeSubDomains as your standard HSTS configuration. Only implement HSTS after confirming all resources (including third-party content) load over HTTPS. Consider preload list submission only after running HSTS successfully for several months.
Mistake #4: X-Frame-Options Set to ALLOW-FROM
Some developers use an outdated X-Frame-Options configuration:
X-Frame-Options: ALLOW-FROM https://example.com
The ALLOW-FROM directive is deprecated and not supported in modern browsers. Don't rely on it for security.
How to avoid it: Use only the supported values:
DENY- Prevent framing completelySAMEORIGIN- Allow framing only by same-origin pages- (omit the header or don't use ALLOW-FROM)
For anything more nuanced than "same origin only" — such as allowing a specific set of partner domains to frame you — use the CSP frame-ancestors directive, which is the modern replacement and supports multiple origins. See X-Frame-Options vs. CSP frame-ancestors for how the two interact and which wins.
Mistake #5: Inconsistent Header Implementation Across Environments
Different environments (development, staging, production) sometimes have different security header configurations:
# Production
Strict-Transport-Security: max-age=31536000; includeSubDomains
# Development
# (HSTS not configured)
This creates a disconnect where development doesn't match production, meaning issues aren't caught until deployment.
How to avoid it: Use identical security header configurations across all environments. If specific configurations are needed for development (like allowing unsafe-inline for development tools), use environment-specific configuration management rather than manual differences. This ensures consistent testing.
Mistake #6: Referrer-Policy Too Permissive
Setting overly permissive referrer policies leaks user data:
Referrer-Policy: unsafe-url
This sends the full URL as referrer to all destinations, potentially including sensitive information in query parameters.
How to avoid it: Use privacy-respecting policies:
Referrer-Policy: strict-origin-when-cross-origin
This balances functionality (same-origin requests get full URLs) with privacy (cross-origin requests get only the origin).
Mistake #7: Neglecting to Update Headers When Third-Party Resources Change
A common pattern: implement strict CSP, then add a third-party service later without updating CSP:
# Original CSP
Content-Security-Policy: default-src 'self'
# Application adds analytics...
# (CSP is never updated)
The application breaks because the analytics script violates CSP, and you either remove CSP or use overly permissive configurations to work around it.
How to avoid it: Implement a process where adding third-party resources requires updating relevant headers. Document every CSP exception with the reason and which third-party service necessitates it. Monitor CSP violation reports to catch unexpected changes. Our guide on handling CSP for third-party resources covers nonces, hashes, and strict-dynamic for exactly this problem.
Mistake #8: Misunderstanding Permissions-Policy Scope
Permissions-Policy is sometimes thought to provide complete API protection:
Permissions-Policy: camera=()
This prevents the site from using camera through the camera API, but it doesn't prevent web-based screen recording or other indirect access to video capture.
How to avoid it: Understand that Permissions-Policy controls specific browser APIs, not all possible data access. Combine it with other security controls like CSP (to prevent scripts that might try to abuse APIs) and CORS headers (to prevent cross-origin API requests).
Mistake #9: Public Reporting of CSP Violations
Some developers expose CSP violation reports publicly:
Content-Security-Policy: default-src 'self'; report-uri /public/csp-violations
CSP reports can reveal application architecture and third-party services you use, providing reconnaissance information to attackers.
How to avoid it: Report CSP violations to an endpoint that requires authentication or is not publicly accessible. Monitor these reports internally rather than exposing them:
Content-Security-Policy: default-src 'self'; report-uri /admin/csp-violations
Mistake #10: Implementing Headers Without Testing Across Browsers
Headers sometimes work in some browsers but not others:
X-Content-Type-Options: nosniff
Works in modern browsers but might interact unexpectedly with older browsers in particular environments.
How to avoid it: Test security headers across target browsers. Use tools like caniuse.com to verify browser support. Test not just that headers are sent, but that they have the intended security effect in each browser you support.
Mistake #11: Not Accounting for Browser Parsing Differences
CSP and other headers with complex syntax can be parsed differently across browsers:
Content-Security-Policy: script-src 'self' https: 'unsafe-inline'
Different browsers interpret https: differently (some allow any HTTPS source, others require specific domains). The header's behavior might vary across browsers.
How to avoid it: Use explicit domain whitelists rather than protocol wildcards:
Content-Security-Policy: script-src 'self' cdn.trusted.com
Test in each target browser to ensure behavior matches your expectations.
Mistake #12: Setting Wrong Directives for Your Use Case
Some organizations implement headers that don't match their threat model:
X-Frame-Options: DENY
If you legitimately need to iframe your application for partner integration, DENY breaks functionality. SAMEORIGIN would be more appropriate.
How to avoid it: Before implementing each header, understand what threats it addresses and whether those threats apply to your application. For example, if you don't process payments, you might prioritize different headers than a payment processor would.
Mistake #13: Forgetting to Apply Headers to Error Responses
Security headers sometimes only appear on successful responses:
If 404 or 500 error pages don't include security headers, attackers gain information about your application's behavior without the same protection.
How to avoid it: Configure your web server to apply security headers to all responses, including error pages. Use the always directive in Apache or equivalents in other servers:
Header always set X-Content-Type-Options "nosniff"
Mistake #14: Not Combining Security Headers with Other Controls
Implementing security headers but neglecting other protections:
Content-Security-Policy: default-src 'self'
# (No HTTPS enforcement, no authentication, weak dependencies)
CSP alone doesn't protect against all attack vectors. It should be one layer in defense-in-depth.
How to avoid it: Implement security headers as part of a comprehensive strategy including:
- HTTPS everywhere (enforced by HSTS)
- Input validation and output encoding
- Regular security updates for dependencies
- Authentication and authorization controls
- Security testing (SAST, DAST, penetration testing)
Mistake #15: Header Syntax Errors
Simple typos or formatting mistakes render headers ineffective:
Strict-Transport-Security max-age=31536000; includeSubDomains
# Missing colon after header name
Content-Security-Policy: defualt-src 'self'
# Typo in directive name (defualt vs default)
These malformed headers might be silently ignored by browsers, creating false confidence in security.
How to avoid it: Use automated validation tools to check header syntax. Security Header Analyzers and browser developer tools catch these issues. Use linting tools in your build process to validate headers before deployment.
Testing for Misconfigurations
Scan a live URL below to see exactly which headers ship, which are missing, and which are misconfigured:
Use these tools to catch misconfigurations:
Security Headers Analyzer - Scans your site and reports missing or misconfigured headers.
Mozilla Observatory - Provides detailed grades and recommendations for security headers.
Browser Developer Tools - Show what headers were actually sent and whether they're valid.
Automated Testing - Integrate header validation into your CI/CD pipeline to catch configuration changes before they reach production.
Avoiding Misconfiguration Through Process
Beyond technical controls, implement process improvements:
- Documentation - Document why each header is configured as it is
- Code Review - Review header changes just like you review code
- Testing - Test header functionality alongside application testing
- Monitoring - Monitor for CSP violations and other header-related issues
- Updates - Review headers periodically as new headers and directives are introduced
Conclusion: Effective Header Implementation Requires Attention to Detail
Security headers are powerful when correctly configured but can provide false confidence when misconfigured. Avoiding these common mistakes requires understanding not just what each header does, but why you're implementing it and what threats it actually protects against. Use automated tools to catch syntax errors and configuration issues, combine headers with other security controls, and maintain ongoing monitoring to ensure headers continue protecting your application as it evolves.