nginx: preserve security baseline in every location; install on VPS
add_header is non-additive: any location declaring its own add_header drops all server-context headers. archive.conf already re-included the baseline for exactly this reason, but static-assets.conf (four cache locations — including the JS/CSS responses where nosniff matters most) and popup-proxy.conf (three proxy locations) did not. All seven now re-include snippets/security-headers.conf. Proxy locations additionally hide the upstream's own STS/CSP/X-Frame-Options before re-adding ours: browsers honor only the FIRST Strict-Transport-Security header (RFC 6797 §8.1), so arXiv's max-age=300 passing through ahead of ours would have downgraded the domain's cached HSTS policy on every popup fetch. Server side (installed + verified live): security-headers.conf and archive.conf wired into the vhost in vhost.conf.example's canonical order; nginx-mod-brotli installed and loaded, so the .br sidecars compress-assets.sh has always shipped are now actually served (Content-Encoding: br verified). CSP remains Report-Only. Verified headers on /, /css/*.css (baseline + Cache-Control together), /archive/ (baseline + X-Robots-Tag), and /proxy/* (baseline + X-Cache-Status, single STS). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
23250d8782
commit
59fcc15ca6
|
|
@ -43,6 +43,18 @@ location /proxy/arxiv/ {
|
|||
proxy_set_header User-Agent "levineuwirth.org popup-proxy (ln@levineuwirth.org)";
|
||||
proxy_ssl_server_name on;
|
||||
|
||||
# Keep the security baseline: the add_header directives below
|
||||
# would otherwise drop it for /proxy/ responses (same pattern
|
||||
# as archive.conf). The upstream's own security headers are hidden
|
||||
# first — browsers honor only the FIRST Strict-Transport-Security
|
||||
# header (RFC 6797 §8.1), so an upstream's short max-age passing
|
||||
# through ahead of ours would downgrade the domain's cached HSTS
|
||||
# policy on every popup fetch.
|
||||
proxy_hide_header Strict-Transport-Security;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
include snippets/security-headers.conf;
|
||||
|
||||
proxy_cache popup_proxy;
|
||||
proxy_cache_valid 200 30d;
|
||||
proxy_cache_valid any 5m;
|
||||
|
|
@ -68,6 +80,18 @@ location /proxy/archive/ {
|
|||
proxy_set_header User-Agent "levineuwirth.org popup-proxy (ln@levineuwirth.org)";
|
||||
proxy_ssl_server_name on;
|
||||
|
||||
# Keep the security baseline: the add_header directives below
|
||||
# would otherwise drop it for /proxy/ responses (same pattern
|
||||
# as archive.conf). The upstream's own security headers are hidden
|
||||
# first — browsers honor only the FIRST Strict-Transport-Security
|
||||
# header (RFC 6797 §8.1), so an upstream's short max-age passing
|
||||
# through ahead of ours would downgrade the domain's cached HSTS
|
||||
# policy on every popup fetch.
|
||||
proxy_hide_header Strict-Transport-Security;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
include snippets/security-headers.conf;
|
||||
|
||||
proxy_cache popup_proxy;
|
||||
proxy_cache_valid 200 7d;
|
||||
proxy_cache_valid any 5m;
|
||||
|
|
@ -96,6 +120,18 @@ location /proxy/pubmed/ {
|
|||
# caching this is rarely exercised, but the burst guards a hot page.
|
||||
limit_req zone=pubmed burst=3 nodelay;
|
||||
|
||||
# Keep the security baseline: the add_header directives below
|
||||
# would otherwise drop it for /proxy/ responses (same pattern
|
||||
# as archive.conf). The upstream's own security headers are hidden
|
||||
# first — browsers honor only the FIRST Strict-Transport-Security
|
||||
# header (RFC 6797 §8.1), so an upstream's short max-age passing
|
||||
# through ahead of ours would downgrade the domain's cached HSTS
|
||||
# policy on every popup fetch.
|
||||
proxy_hide_header Strict-Transport-Security;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
include snippets/security-headers.conf;
|
||||
|
||||
proxy_cache popup_proxy;
|
||||
proxy_cache_valid 200 30d;
|
||||
proxy_cache_valid any 5m;
|
||||
|
|
|
|||
|
|
@ -55,16 +55,26 @@ brotli off; # we ship pre-compressed sidecars only, no on-the-fly b
|
|||
# instantaneous. Same reasoning applies to fingerprinted fonts and the
|
||||
# locally vendored ML model files.
|
||||
location ^~ /pdfjs/ {
|
||||
# Re-include the security baseline: this location declares its
|
||||
# own add_header, which (per nginx inheritance rules) would
|
||||
# otherwise drop HSTS/nosniff/CSP for everything it serves —
|
||||
# and nosniff matters most on exactly these JS/CSS responses.
|
||||
# Same pattern as archive.conf.
|
||||
include snippets/security-headers.conf;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location ^~ /fonts/ {
|
||||
# Keep the security baseline (see first location above).
|
||||
include snippets/security-headers.conf;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location ^~ /models/ {
|
||||
# Keep the security baseline (see first location above).
|
||||
include snippets/security-headers.conf;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
access_log off;
|
||||
}
|
||||
|
|
@ -74,6 +84,8 @@ location ^~ /models/ {
|
|||
# keeps them responsive to deploys (~1h staleness window for warm clients)
|
||||
# without forcing a fetch on every page navigation.
|
||||
location ~* \.(?:css|js|mjs|woff2?|svg|webp|png|jpg|jpeg|ico)$ {
|
||||
# Keep the security baseline (see first location above).
|
||||
include snippets/security-headers.conf;
|
||||
add_header Cache-Control "public, max-age=3600, must-revalidate" always;
|
||||
access_log off;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue