HTTP Security Headers
HTTP security headers are response headers that the web server sends with every response to tell the browser how to behave securely—which resources it is allowed to load, whether pages can be embedded in frames, and whether cookies are sent only over HTTPS. They are easy to implement and protect against a range of attacks.
HTTP security headers are one of the easiest ways to improve security in web development: just a few lines of configuration in the web server or code can protect against XSS, clickjacking, MIME type sniffing, and enforce HTTPS. Yet they are missing from an alarming number of live websites—they cost nothing but provide significant protection.
The Most Important Security Headers
Content-Security-Policy (CSP) - Protection against XSS:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM_PER_REQUEST}';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.firma.de;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
Explanation:
default-src 'self' → Load only from own origin
script-src nonce → Scripts require a per-request nonce
frame-ancestors 'none' → Clickjacking protection (like X-Frame-Options)
upgrade-insecure-requests → HTTP links are upgraded to HTTPS
base-uri 'self' → Prevent base tag injection
Getting Started: Report-Only Mode:
Content-Security-Policy-Report-Only:
default-src 'self'; report-uri /csp-report
→ No blocking, only reports → good for testing!
---
Strict-Transport-Security (HSTS) - Enforce HTTPS:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age=31536000 → 1-year HTTPS requirement stored in the browser
includeSubDomains → Also applies to all subdomains
preload → Added to browser preload list
HSTS Preload: hstspreload.org
→ Browser never connects via HTTP - prevents SSL stripping!
→ Caution: Use `includeSubDomains` only when ALL subdomains have HTTPS!
---
X-Frame-Options - Clickjacking protection:
X-Frame-Options: DENY
→ Page must not be embedded in an iframe anywhere
X-Frame-Options: SAMEORIGIN
→ Can only be embedded from your own domain
Note: CSP frame-ancestors is more modern and flexible!
Set both for maximum browser compatibility.
---
X-Content-Type-Options - Prevent MIME sniffing:
X-Content-Type-Options: nosniff
Prevents: Browser guesses MIME type even if server declares it incorrectly
→ HTML file as text/plain → Browser would not execute it as HTML
→ Attack: Upload an image that is actually JavaScript
---
Referrer-Policy - Control referrer information:
Referrer-Policy: strict-origin-when-cross-origin
Options:
no-referrer → No Referrer header sent
no-referrer-when-downgrade → No Referrer for HTTP→HTTP
origin → Only origin (no paths!)
strict-origin-when-cross-origin → Origin for cross-site, full for same-site
Problem without policy:
User is on https://intern.firma.de/geheimprojekt
→ Clicks on external link
→ Referrer: https://intern.firma.de/geheimprojekt → External site sees URL!
---
Permissions-Policy - Control browser features:
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(),
payment=(),
usb=()
→ Disables access to camera, microphone, GPS, and payment API for embedded content
→ Previously: Feature-Policy header
---
Cross-Origin Headers (Configure CORS correctly):
Access-Control-Allow-Origin: https://app.firma.de
→ NOT: * (if credentials are sent!)
Access-Control-Allow-Credentials: true
→ Only if cookies/authentication are truly required!
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Dangerous misconfiguration:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
→ INVALID! Browsers ignore this—but some libraries do not!
Implementation in the Web Server and Code
nginx:
server {
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;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'; upgrade-insecure-requests;" always;
# "always" → send even with 4xx/5xx responses!
}
---
Apache:
<IfModule mod_headers.c>
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"---
</IfModule>
Express.js (Node.js) with Helmet:
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
frameAncestors: ["'none'"],
upgradeInsecureRequests: [],
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
frameguard: { action: 'deny' },
noSniff: true,
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}));
---
Astro.js (SSG/SSR):
// astro.config.mjs:
export default defineConfig({
server: {
headers: {
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
}
}
});
Security Header Testing
Online Tools:
→ securityheaders.com: Report for each URL
→ Mozilla Observatory (observatory.mozilla.org): A-F Score
→ HSTS Preload Check: hstspreload.org
Local Tests:
# curl check:
curl -I https://yoursite.de | grep -i "content-security\|x-frame\|strict-transport\|x-content\|referrer"
# nikto (HTTP Header Check):
nikto -h https://yoursite.de -Plugins headers
Scoring:
Mozilla Observatory Score:
A+ (100/100): All important headers set, strict CSP
B (55-74): Most headers, CSP present but lenient
D (<40): Missing HSTS or CSP
Goal: A or A+ on Mozilla Observatory
Typical findings in the pentest (missing headers):
□ No HSTS → SSL stripping possible
□ No X-Frame-Options → Clickjacking possible
□ No X-Content-Type-Options → MIME sniffing
□ No CSP → XSS without mitigation
□ Wide CORS (*) → Cross-origin access
□ Server header shows version: "Server: Apache/2.4.51"
→ Remove version disclosure: ServerTokens Prod (Apache)