Security (as discussed in OWASP ZAP Automation: Security Scanning in CI/CD) headers are HTTP response headers that enhance web application security by instructing browsers how to behave when handling your site’s content. Proper implementation protects against common attacks like XSS (as discussed in Penetration Testing Basics for QA Testers), clickjacking, and man-in-the-middle attacks.
Essential Security Headers
1. Content-Security-Policy (CSP)
Purpose: Prevents XSS attacks by controlling resource loading
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-ancestors 'none';
Testing:
def test_csp_header():
response = requests.get("https://example.com")
csp = response.headers.get('Content-Security-Policy')
assert csp is not None, "CSP header missing"
assert "default-src 'self'" in csp, "CSP too permissive"
assert "'unsafe-eval'" not in csp, "unsafe-eval should be avoided"
2. Strict-Transport-Security (HSTS)
Purpose: Forces HTTPS connections
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Testing:
def test_hsts_header():
response = requests.get("https://example.com")
hsts = response.headers.get('Strict-Transport-Security')
(as discussed in [Security Testing for QA: A Practical Guide](/blog/security-testing-for-qa)) assert hsts is not None, "HSTS header missing"
assert 'max-age=' in hsts, "max-age directive missing"
# Extract max-age value
max_age = int(hsts.split('max-age=')[1].split(';')[0])
assert max_age >= 31536000, "max-age should be at least 1 year"
assert 'includeSubDomains' in hsts, "Should include subdomains"
3. X-Frame-Options
Purpose: Prevents clickjacking attacks
X-Frame-Options: DENY
# or
X-Frame-Options: SAMEORIGIN
Testing:
def test_x_frame_options():
response = requests.get("https://example.com")
xfo = response.headers.get('X-Frame-Options')
assert xfo is not None, "X-Frame-Options missing"
assert xfo in ['DENY', 'SAMEORIGIN'], f"Invalid value: {xfo}"
4. X-Content-Type-Options
Purpose: Prevents MIME-sniffing
X-Content-Type-Options: nosniff
Testing:
def test_x_content_type_options():
response = requests.get("https://example.com")
xcto = response.headers.get('X-Content-Type-Options')
assert xcto == 'nosniff', "X-Content-Type-Options should be 'nosniff'"
5. X-XSS-Protection
Purpose: Enables browser XSS protection (legacy)
X-XSS-Protection: 1; mode=block
Note: Deprecated in favor of CSP, but still useful for older browsers
6. Referrer-Policy
Purpose: Controls referrer information
Referrer-Policy: strict-origin-when-cross-origin
Testing:
def test_referrer_policy():
response = requests.get("https://example.com")
rp = response.headers.get('Referrer-Policy')
allowed_values = [
'no-referrer',
'no-referrer-when-downgrade',
'origin',
'origin-when-cross-origin',
'same-origin',
'strict-origin',
'strict-origin-when-cross-origin'
]
assert rp in allowed_values, f"Invalid Referrer-Policy: {rp}"
7. Permissions-Policy
Purpose: Controls browser features and APIs
Permissions-Policy: geolocation=(), microphone=(), camera=()
Testing:
def test_permissions_policy():
response = requests.get("https://example.com")
pp = response.headers.get('Permissions-Policy')
assert pp is not None, "Permissions-Policy missing"
assert 'geolocation=()' in pp, "Should disable geolocation"
assert 'camera=()' in pp, "Should disable camera"
Comprehensive Security Headers Test Suite
import requests
import pytest
class TestSecurityHeaders:
base_url = "https://example.com"
def test_all_security_headers(self):
response = requests.get(self.base_url)
headers = response.headers
# Required headers
required_headers = {
'Content-Security-Policy': self.validate_csp,
'Strict-Transport-Security': self.validate_hsts,
'X-Frame-Options': self.validate_xfo,
'X-Content-Type-Options': self.validate_xcto,
'Referrer-Policy': self.validate_referrer,
}
for header_name, validator in required_headers.items():
assert header_name in headers, f"{header_name} missing"
validator(headers[header_name])
def validate_csp(self, value):
assert "default-src" in value
assert "'unsafe-eval'" not in value
def validate_hsts(self, value):
assert "max-age=" in value
max_age = int(value.split('max-age=')[1].split(';')[0])
assert max_age >= 15768000 # 6 months minimum
def validate_xfo(self, value):
assert value in ['DENY', 'SAMEORIGIN']
def validate_xcto(self, value):
assert value == 'nosniff'
def validate_referrer(self, value):
allowed = [
'no-referrer', 'strict-origin',
'strict-origin-when-cross-origin'
]
assert value in allowed
def test_no_information_disclosure(self):
response = requests.get(self.base_url)
headers = response.headers
# Should not expose server information
if 'Server' in headers:
server = headers['Server'].lower()
assert 'apache' not in server, "Server version exposed"
assert 'nginx' not in server, "Server version exposed"
# Should not expose technology stack
assert 'X-Powered-By' not in headers, "X-Powered-By should be removed"
assert 'X-AspNet-Version' not in headers, "ASP.NET version exposed"
Automated Testing Tools
1. SecurityHeaders.com
# Using curl
curl -I https://example.com | grep -E "(Content-Security-Policy|Strict-Transport-Security|X-Frame-Options)"
2. Observatory by Mozilla
import requests
def test_mozilla_observatory():
url = "https://http-observatory.security.mozilla.org/api/v1/analyze"
params = {"host": "example.com"}
response = requests.post(url, params=params)
scan_id = response.json()['scan_id']
# Wait for scan to complete
result_url = f"https://http-observatory.security.mozilla.org/api/v1/getScanResults?scan={scan_id}"
result = requests.get(result_url).json()
assert result['grade'] in ['A+', 'A', 'B'], f"Security grade: {result['grade']}"
3. Custom Script
#!/bin/bash
# security-headers-check.sh
URL=$1
echo "Checking security headers for: $URL"
headers=(
"Content-Security-Policy"
"Strict-Transport-Security"
"X-Frame-Options"
"X-Content-Type-Options"
"Referrer-Policy"
)
for header in "${headers[@]}"; do
value=$(curl -s -I "$URL" | grep -i "^$header:" | cut -d' ' -f2-)
if [ -z "$value" ]; then
echo "❌ $header: MISSING"
else
echo "✅ $header: $value"
fi
done
Implementation Guide
Node.js/Express
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
frameguard: {
action: 'deny'
},
referrerPolicy: {
policy: 'strict-origin-when-cross-origin'
}
}));
Python/Django
# settings.py
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "https://cdn.example.com")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com;" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" 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;
# Remove server version
server_tokens off;
CI/CD Integration
# .github/workflows/security-headers.yml
name: Security Headers Test
on: [push, pull_request]
jobs:
test-headers:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Security Headers Test
run: |
pytest tests/test_security_headers.py
- name: Check with securityheaders.com
run: |
curl -s "https://securityheaders.com/?q=${{ secrets.PRODUCTION_URL }}&hide=on&followRedirects=on" | grep "Grade: A"
Conclusion
Security headers are a crucial defense layer for web applications. Regular testing ensures proper implementation and helps protect against common web vulnerabilities.
Key Takeaways:
- Implement all essential security headers
- Test headers in CI/CD pipeline
- Use automated scanning tools
- Monitor header effectiveness
- Keep headers updated with security best practices
- Remove information disclosure headers