APS-100Recommended
Send the security headers as one set
Set every browser security header in one middleware or server block that runs for every response, including errors, redirects, files and API answers. Test the whole set as one unit.
How to do it
The set is Strict-Transport-Security, Content-Security-Policy with frame-ancestors and, where your code allows, require-trusted-types-for 'script'. It also holds X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy, Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy. Add Cross-Origin-Embedder-Policy only when you need cross-origin isolation. COOP, CORP and COEP work together, as Keep other sites' windows apart explains. Trusted Types is part of the policy, as Lock down risky DOM writes shows. A header set per route gets forgotten on error pages, which attackers request on purpose. Keep the values in one constant, so a change shows in one diff.
Examples
JavaScript example
const SECURITY_HEADERS = {
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains; preload',
'Content-Security-Policy': "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; require-trusted-types-for 'script'",
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Resource-Policy': 'same-site',
};
app.use((req, res, next) => { res.set(SECURITY_HEADERS); next(); });
Python example
SECURITY_HEADERS = {
"Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
"Content-Security-Policy": "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; require-trusted-types-for 'script'",
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Permissions-Policy": "camera=(), microphone=(), geolocation=()",
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Resource-Policy": "same-site",
}
class SecurityHeadersMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
for name, value in SECURITY_HEADERS.items():
response.setdefault(name, value)
return response
How to check
A test requests the home page, a missing page, a server error page, a redirect, a static file and an API route. Each answer carries Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy.
Sources
Related rules
- APS-30 Keep other sites' windows apart in Application security
- APS-35 Lock down risky DOM writes in Application security
- APS-27 Stop browsers guessing file types in Application security
- APS-28 Refuse to be framed in Application security
- APS-32 Switch off browser features you don't use in Application security
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/application-security/browser-security-headers/header-set">Agent Wranglers application security playbook</a>. In code you write, add a comment next to it with the same link.