Security Headers

Risk Severity
๐ŸŸก Medium
Fix Effort
โšก Low (Quick Fix)
Est. Time
โฑ๏ธ 30 minutes
Reference
A05:2021 CWE-693

HTTP security headers provide an extra layer of security by helping to detect and mitigate certain types of attacks, including XSS and data injection.

Easy Wins

Security headers are one of the simplest security improvements you can make. Most can be added with a single line of configuration and provide defense-in-depth against multiple attack types.

Recommended Headers

Each header below addresses specific attack vectors. Implementing all of them provides comprehensive browser-level protection.

All applications should implement these headers:

http
# Prevent clickjacking
X-Frame-Options: DENY

# Enable browser XSS filter
X-XSS-Protection: 1; mode=block

# Prevent MIME-type sniffing
X-Content-Type-Options: nosniff

# Force HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

# Control referrer information
Referrer-Policy: strict-origin-when-cross-origin

# Permissions Policy (formerly Feature-Policy)
Permissions-Policy: geolocation=(), microphone=(), camera=()

# Content Security Policy
Content-Security-Policy: default-src 'self'

X-Frame-Options

Prevents your site from being embedded in iframes on other domains, blocking clickjacking attacks where attackers overlay invisible frames to hijack clicks.

X-Content-Type-Options

Stops browsers from guessing ("sniffing") content types. Prevents attacks where a file served as text/plain is executed as JavaScript because the browser detected script content.

Strict-Transport-Security (HSTS)

Forces browsers to only connect via HTTPS for the specified duration. Prevents SSL stripping attacks and accidental HTTP connections. Add preload for browser built-in list.

Referrer-Policy

Controls how much referrer information is sent with requests. strict-origin-when-cross-origin sends full path to same-origin, only origin to cross-origin, nothing to less secure.

Permissions-Policy

Controls which browser features (camera, microphone, geolocation) your site can use. Empty parentheses () disable the feature entirely. Limits damage if XSS occurs.

Content-Security-Policy

The most powerful security header. Defines allowed sources for scripts, styles, images, etc. default-src 'self' only allows resources from your own domain.

Implementation

Here's how to implement these headers in popular frameworks and web servers. Most provide middleware or configuration options to set headers globally.

Express.js (Node.js)

Using helmet:

javascript
const helmet = require('helmet');
app.use(helmet());  // Enables most security headers

// Or configure individually:
app.use(helmet.frameguard({ action: 'deny' }));
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
app.use(helmet.noSniff());
app.use(helmet.xssFilter());

Flask (Python)

Using flask-talisman:

python
from flask_talisman import Talisman
Talisman(app, 
    force_https=True,
    strict_transport_security=True,
    strict_transport_security_max_age=31536000,
    content_security_policy=csp
)

Django

In settings.py:

python
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_SSL_REDIRECT = True

Web Server Config

Apache (.htaccess):

apache
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set Referrer-Policy "strict-origin-when-cross-origin"

Nginx:

nginx
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

๐Ÿงช Testing Verification

Use these commands and tools to verify your security headers are properly configured.

Command Line Tests

bash
# Check all security headers at once
curl -I https://yoursite.com | grep -iE "x-frame|x-content|strict-transport|referrer|content-security|permissions"

# Expected output should include:
# X-Frame-Options: DENY
# X-Content-Type-Options: nosniff
# Strict-Transport-Security: max-age=31536000; includeSubDomains
# Referrer-Policy: strict-origin-when-cross-origin
# Content-Security-Policy: default-src 'self'

Online Testing Tools

securityheaders.com

Comprehensive header analysis with grading

observatory.mozilla.org

Mozilla's security scanner with recommendations

csp-evaluator.withgoogle.com

Google's CSP analysis tool

hstspreload.org

Check HSTS preload list eligibility

โš ๏ธ Common Mistakes

โŒ Missing "always" in Nginx

nginx
# DON'T: Headers only on 2xx responses
add_header X-Frame-Options "DENY";

Without "always", headers missing on error pages

โœ… Correct Approach

nginx
# DO: Include "always" for all responses
add_header X-Frame-Options "DENY" always;

Headers sent on all response codes

โŒ HSTS Without HTTPS

text
# DON'T: Enable HSTS before HTTPS works
# If HTTPS breaks, users can't access site!

Test HTTPS thoroughly before enabling HSTS

โœ… Correct Approach

text
# DO: Start with short max-age, then increase
max-age=300  # 5 min (testing)
max-age=31536000  # 1 year (production)

Gradually increase HSTS duration

โŒ Headers Set in Wrong Location

nginx
# DON'T: Set in location block only
location /api {
    add_header X-Frame-Options "DENY";
}
# Other locations unprotected!

Headers not applied to all routes

โœ… Correct Approach

nginx
# DO: Set in server block for all routes
server {
    add_header X-Frame-Options "DENY" always;
    # Applies to all locations
}

Headers applied globally