# security-headers.conf — security baseline for the levineuwirth.org vhost.
#
# Place at /etc/nginx/snippets/security-headers.conf and `include` it
# inside the server { } block of the vhost, alongside the other
# snippets shipped from this repo:
#
#   server {
#       server_name levineuwirth.org;
#       root /var/www/levineuwirth.org;
#       ...
#       include snippets/security-headers.conf;
#       include snippets/static-assets.conf;
#       include snippets/popup-proxy.conf;
#   }

# Hide the nginx version from error pages and the Server header.
server_tokens off;

# HSTS — one year, with subdomains, preload-eligible. Only safe to
# enable once HTTPS is the only listener (the :80 vhost should already
# 301 → https). Once preload is requested via hstspreload.org, the
# max-age cannot be lowered without removing from the list manually.
add_header Strict-Transport-Security
    "max-age=31536000; includeSubDomains; preload" always;

# Block MIME-sniffing — required for safe text/plain and svg responses.
add_header X-Content-Type-Options "nosniff" always;

# Defense against clickjacking. Modern browsers honor CSP frame-ancestors
# (set below); X-Frame-Options is kept for legacy clients.
add_header X-Frame-Options "DENY" always;

# Strip the Referer header to "scheme + host" on cross-origin requests.
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Default-deny powerful APIs. interest-cohort opts out of FLoC/Topics.
add_header Permissions-Policy
    "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;

# Content Security Policy. Shipped in Report-Only mode initially —
# promote to enforcing (strip the "-Report-Only" suffix) once the
# report stream has been clean for a week.
#
# External origins justified inline:
#   cdn.jsdelivr.net               KaTeX CSS + JS + webfonts (the KaTeX CSS
#                                  references its fonts relatively, so they
#                                  resolve to the CDN -> font-src), Vega /
#                                  Vega-Lite / Vega-Embed, transformers.js
#                                  (whose onnxruntime fetches its .wasm from
#                                  the CDN via fetch() -> connect-src)
#   *.basemaps.cartocdn.com        Leaflet basemap tiles (photography map only)
#   connect-src API hosts          link-popup providers fetched directly via
#                                  CORS (the list popups.js documents in its
#                                  header, plus git.levineuwirth.org for the
#                                  Forgejo provider). The CORS-broken trio
#                                  (arxiv, archive.org, pubmed) goes through
#                                  the same-origin /proxy/ instead — see
#                                  nginx/popup-proxy.conf.
#
# Why 'unsafe-inline' on style:
#   - photography.html emits <span style="background:$swatch$"> for
#     palette swatches (data-driven, can't be hashed).
#   - KaTeX adds inline styles when rendering math at runtime.
#
# Why 'unsafe-eval' on script:
#   - vega-embed compiles Vega-Lite specs at runtime via new Function().
#     Removing this would require pre-compiling specs at build time.
#   - it also covers WebAssembly.instantiate for onnxruntime-web
#     (semantic search).
#
# The value MUST stay on one physical line: nginx has no line
# continuation inside quoted strings — a trailing backslash would embed
# literal backslash + LF bytes in the header value, which is illegal in
# HTTP/2 and gets whole responses rejected by strict clients.
#
# To collect violation reports, set up a `report-uri` endpoint and add
# `report-uri /csp-report;` (and/or `report-to <group>;`) below.
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-eval' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https://*.basemaps.cartocdn.com https://upload.wikimedia.org;font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self' https://cdn.jsdelivr.net https://*.wikipedia.org https://api.crossref.org https://api.github.com https://openlibrary.org https://api.biorxiv.org https://www.youtube.com https://git.levineuwirth.org; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests" always;
