Security controls are delivered on HTTP responses, so their coverage follows the actual response producer. A static asset, a server-rendered document, an API error, and a redirect may take different paths through a hosting platform. A configuration file that decorates static assets can leave function responses untouched. Define the required headers per response class, then probe the public hostname and record the response source. Check the final response after redirects; a development server can use different middleware and must not stand in for production evidence. Some headers should vary by route rather than being copied blindly onto every response.
Security headers: verify static, server-rendered, and error responses independently
Operational decision
A portal deploys static CSS and server-rendered lesson HTML. The static asset responds with a policy header, but the lesson route is generated by a server function and lacks it. The release gate samples one static asset, one lesson, one API error, and one missing-route response through the public hostname. The function attaches the intended policy itself, while the static rule remains responsible for assets. The operator compares headers after following redirects and checks that a cache does not retain a weaker earlier response. When a new route type is added, it joins this response-class matrix before the team calls the policy complete.
set -euo pipefail
: "${SITE_ORIGIN:?set the public origin}"
: "${STATIC_ASSET_PATH:?set a deployed asset path}"
headers=$(mktemp)
trap 'rm -f "$headers"' EXIT
for route in /devops/getting-started "$STATIC_ASSET_PATH" /missing-release-probe; do
status=$(curl --silent --show-error --location --output /dev/null --dump-header "$headers" --write-out '%{http_code}' "${SITE_ORIGIN}${route}")
printf '%s status=%s policy=' "$route" "$status"
awk 'BEGIN { policy = "missing" } /^HTTP\// { policy = "missing" } tolower($0) ~ /^content-security-policy:/ { policy = $0 } END { print policy }' "$headers"
doneCost and verification
The probe makes one request per response class, so testing C classes costs O(C) requests and header bytes. The sample is an inspection aid, not a pass/fail gate: a missing CSS path or an error response might intentionally use a different policy. A production gate should assert the expected status and headers for each named route, including no-store on private responses. Large header sets add response bytes; more serious is false confidence from checking only the static homepage while server-rendered pages remain unprotected.
Common Mistakes
- Do not assume a static header rule covers server functions.
- Do not inspect only the first redirect response.
- Do not mark a security policy complete after probing one route type.
Connected lessons
- DevOps: delivery, infrastructure, and reliable operations
- Content Security Policy rollout: turn observed violations into an enforceable rule
- Web publishing evidence: prove the build, deployment, and live route separately
- Edge cache fallback: serve stale public data without leaking private responses
