Agent Wranglers

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

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.