Skip to main content

Overview

Security headers are HTTP response headers that provide defense-in-depth protection against common web vulnerabilities. This guide demonstrates the security headers implementation from the secure application.

Why Security Headers Matter

Defense in Depth

Additional protection layer even if other defenses fail.

Browser Security

Leverage browser security features to protect users.

XSS Prevention

Restrict script execution to prevent XSS attacks.

Clickjacking Protection

Prevent your site from being embedded in malicious frames.

Implementation in Flask

Set security headers globally using Flask’s after_request decorator:
secure/app.py
@app.after_request
def set_security_headers(response):
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    response.headers['Content-Security-Policy'] = "default-src 'self'"
    return response
The @app.after_request decorator ensures these headers are added to every response automatically.

Essential Security Headers

X-Content-Type-Options

Prevents MIME-type sniffing attacks by forcing browsers to respect declared content types.
response.headers['X-Content-Type-Options'] = 'nosniff'

What it prevents

Stops browsers from interpreting files as a different MIME type than declared.

Attack scenario

Attacker uploads image.jpg containing JavaScript; browser might execute it without this header.

X-Frame-Options

Protects against clickjacking attacks by controlling whether your site can be embedded in frames.
response.headers['X-Frame-Options'] = 'DENY'
1

DENY

Completely prevents page from being displayed in a frame:
response.headers['X-Frame-Options'] = 'DENY'
2

SAMEORIGIN

Allows framing only by same domain:
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
3

ALLOW-FROM (deprecated)

Legacy option to specify allowed domains (use CSP frame-ancestors instead).
Use DENY for maximum protection unless you need to embed your site in iframes on your own domain.

X-XSS-Protection

Enables browser’s built-in XSS filtering (legacy header, but still useful for older browsers).
response.headers['X-XSS-Protection'] = '1; mode=block'
Values:
  • 0: Disables XSS filtering
  • 1: Enables XSS filtering
  • 1; mode=block: Blocks page rendering if XSS detected
Modern browsers rely on Content-Security-Policy instead, but this header provides fallback protection.

Strict-Transport-Security (HSTS)

Forces browsers to use HTTPS connections only, preventing downgrade attacks.
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'

max-age

Time (seconds) browser should remember to only use HTTPS:
max-age=31536000
(1 year)

includeSubDomains

Apply HSTS to all subdomains:
includeSubDomains

preload

Opt-in to HSTS preload list:
preload
Start with a short max-age (e.g., 300 seconds) for testing, then increase to 31536000 (1 year) for production.

Content-Security-Policy (CSP)

The most powerful security header - controls which resources can be loaded and executed.
response.headers['Content-Security-Policy'] = "default-src 'self'"
This basic policy only allows resources from the same origin.

Content Security Policy Deep Dive

CSP Directives

default-src

Fallback for all resource types:
default-src 'self'

script-src

Control JavaScript sources:
script-src 'self' https://cdn.example.com

style-src

Control CSS sources:
style-src 'self' 'unsafe-inline'

img-src

Control image sources:
img-src 'self' data: https:

connect-src

Control AJAX/WebSocket sources:
connect-src 'self' https://api.example.com

frame-ancestors

Control who can frame your site:
frame-ancestors 'none'

Basic CSP Example

@app.after_request
def set_security_headers(response):
    csp = (
        "default-src 'self'; "
        "script-src 'self'; "
        "style-src 'self' 'unsafe-inline'; "
        "img-src 'self' data: https:; "
        "font-src 'self'; "
        "connect-src 'self'; "
        "frame-ancestors 'none'"
    )
    response.headers['Content-Security-Policy'] = csp
    return response

Strict CSP Configuration

For maximum security (blocks all inline scripts and styles):
csp = (
    "default-src 'none'; "
    "script-src 'self'; "
    "style-src 'self'; "
    "img-src 'self'; "
    "font-src 'self'; "
    "connect-src 'self'; "
    "frame-ancestors 'none'; "
    "base-uri 'self'; "
    "form-action 'self'"
)
response.headers['Content-Security-Policy'] = csp
This strict policy requires moving all inline JavaScript and CSS to external files.

CSP with CDNs

If using external resources like Bootstrap or jQuery:
csp = (
    "default-src 'self'; "
    "script-src 'self' https://cdn.jsdelivr.net https://code.jquery.com; "
    "style-src 'self' https://cdn.jsdelivr.net; "
    "font-src 'self' https://cdn.jsdelivr.net; "
    "img-src 'self' data: https:; "
)
response.headers['Content-Security-Policy'] = csp
Only whitelist trusted CDNs and use Subresource Integrity (SRI) hashes for additional security.

Additional Security Headers

Referrer-Policy

Controls how much referrer information is sent with requests:
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
Common values:
  • no-referrer: Never send referrer
  • same-origin: Send only for same-origin requests
  • strict-origin-when-cross-origin: Send origin only for cross-origin

Permissions-Policy

Controls which browser features can be used:
response.headers['Permissions-Policy'] = 'geolocation=(), microphone=(), camera=()'
This header replaced the older Feature-Policy header.

Complete Security Headers Implementation

secure/app.py
from flask import Flask

app = Flask(__name__)

@app.after_request
def set_security_headers(response):
    # Prevent MIME sniffing
    response.headers['X-Content-Type-Options'] = 'nosniff'
    
    # Prevent clickjacking
    response.headers['X-Frame-Options'] = 'DENY'
    
    # Enable XSS filter (legacy browsers)
    response.headers['X-XSS-Protection'] = '1; mode=block'
    
    # Force HTTPS
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    
    # Content Security Policy
    response.headers['Content-Security-Policy'] = "default-src 'self'"
    
    # Control referrer information
    response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
    
    # Disable unnecessary browser features
    response.headers['Permissions-Policy'] = 'geolocation=(), microphone=(), camera=()'
    
    return response

Testing Security Headers

Verify headers are set correctly using curl:
curl -I https://yourapp.com
Expected output:
HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'
Use online tools like securityheaders.com to analyze your security headers.

CSP Reporting

Monitor CSP violations by adding report-uri:
csp = (
    "default-src 'self'; "
    "report-uri /csp-violation-report"
)
response.headers['Content-Security-Policy'] = csp
Create an endpoint to receive reports:
@app.route('/csp-violation-report', methods=['POST'])
def csp_report():
    report = request.get_json()
    # Log the violation
    app.logger.warning(f'CSP Violation: {report}')
    return '', 204
CSP reporting helps identify violations without blocking resources during development.

Security Headers Best Practices

1

Start with report-only mode

Test CSP without breaking functionality:
response.headers['Content-Security-Policy-Report-Only'] = csp
2

Use strict policies

Start strict and relax only when necessary, not the other way around.
3

Test thoroughly

Verify headers don’t break legitimate functionality in all browsers.
4

Monitor violations

Set up CSP reporting to catch issues in production.
5

Keep updated

Security header recommendations evolve; review configuration regularly.

Common Pitfalls

'unsafe-inline' in CSP

Avoid 'unsafe-inline' for script-src as it defeats XSS protection.

Missing HSTS on subdomains

Use includeSubDomains to protect all subdomains.

Too permissive CSP

Don’t use * or https: unless absolutely necessary.

Not testing headers

Always verify headers are actually being set in responses.

Framework-Specific Helpers

For easier header management, consider using Flask-Talisman:
from flask import Flask
from flask_talisman import Talisman

app = Flask(__name__)

# Automatically sets secure headers
Talisman(app, 
    force_https=True,
    strict_transport_security=True,
    content_security_policy={
        'default-src': "'self'"
    }
)

Next Steps

Output Encoding

Learn about XSS prevention through output encoding

Secure Configuration

Configure your application securely

Build docs developers (and LLMs) love