add CSP-clean static build, hardened nginx config, and container image
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# nginx config for the static teaching site, baked into the image.
|
||||
# Base: nginxinc/nginx-unprivileged (uid 101, listens on 8080).
|
||||
# Read-only rootfs in k8s: /tmp and /var/cache/nginx are emptyDir mounts.
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
server_tokens off;
|
||||
|
||||
# Behind the Cloudflare Tunnel on :443 while nginx listens on :8080 — without these,
|
||||
# nginx emits ABSOLUTE redirects to http://host:8080/... and breaks every link.
|
||||
absolute_redirect off;
|
||||
port_in_redirect off;
|
||||
|
||||
include /etc/nginx/security-headers.conf;
|
||||
|
||||
# ---- Caching -------------------------------------------------------------
|
||||
# Scene images are content-hashed at build time → safe to cache hard.
|
||||
location /assets/ {
|
||||
include /etc/nginx/security-headers.conf;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable" always;
|
||||
}
|
||||
|
||||
# Scripts are NOT fingerprinted yet — revalidate so a deploy actually propagates.
|
||||
location ~* \.js$ {
|
||||
include /etc/nginx/security-headers.conf;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
}
|
||||
|
||||
location ~* \.html$ {
|
||||
include /etc/nginx/security-headers.conf;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
}
|
||||
|
||||
# ---- Routing -------------------------------------------------------------
|
||||
location / {
|
||||
try_files $uri $uri/ $uri.html =404;
|
||||
}
|
||||
|
||||
# ---- Compression ---------------------------------------------------------
|
||||
# WebP/PNG are already compressed — gzipping them burns CPU for nothing.
|
||||
gzip on;
|
||||
gzip_comp_level 6;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
|
||||
gzip_vary on;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Shared security headers. `include`d in the server block AND in every location that
|
||||
# sets its own add_header — a location-level add_header REPLACES inherited headers
|
||||
# rather than merging them (the classic nginx footgun).
|
||||
#
|
||||
# HSTS is host-scoped: NO includeSubDomains / preload, because the *.bztmon.com
|
||||
# wildcard points elsewhere and forcing HTTPS estate-wide from here would be reckless.
|
||||
# HTTP->HTTPS upgrade is Cloudflare's job.
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
|
||||
# script-src 'self' with NO 'unsafe-inline'. build.sh externalises every script and
|
||||
# hard-fails if an inline <script> reaches dist/, so this holds without an exception.
|
||||
# style-src needs 'unsafe-inline': GSAP animates by writing inline style attributes.
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; worker-src 'self'; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()" always;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
add_header Cross-Origin-Resource-Policy "same-origin" always;
|
||||
Reference in New Issue
Block a user