From 7721bf5cecf6fd10cf5029112b54c4bc14eba875 Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Mon, 2 Mar 2026 17:43:12 +0800 Subject: [PATCH] Harden nginx: real client IP, HSTS, custom dotfile 404 (PT-01/02/04) PT-01: Add set_real_ip_from/real_ip_header/real_ip_recursive to restore real client IP from X-Forwarded-For. Rate limiting now keys on actual client IP instead of the Pangolin proxy IP. PT-02: Add Strict-Transport-Security header (max-age 1 year) to both the server block and static assets block. PT-04: Replace bare 404 on dotfile requests with JSON response to suppress nginx server identity disclosure in error pages. Co-Authored-By: Claude Opus 4.6 --- frontend/nginx.conf | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 1e8bbd8..3a39185 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -21,15 +21,26 @@ server { # Suppress nginx version in Server header server_tokens off; + # ── Real client IP restoration (PT-01) ──────────────────────────── + # Pangolin (TLS-terminating reverse proxy) connects via Docker bridge. + # Restore the real client IP from X-Forwarded-For so that limit_req_zone + # (which keys on $binary_remote_addr) throttles per-client, not per-proxy. + # Safe to trust all sources: nginx is only reachable via Docker networking, + # never directly internet-facing. Tighten if deployment model changes. + set_real_ip_from 0.0.0.0/0; + real_ip_header X-Forwarded-For; + real_ip_recursive on; + # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json; - # Block dotfiles (except .well-known for ACME/Let's Encrypt) + # Block dotfiles (except .well-known for ACME/Let's Encrypt) (PT-04) location ~ /\.(?!well-known) { - return 404; + default_type application/json; + return 404 '{"detail":"Not Found"}'; } # Rate-limited auth endpoints (keep in sync with proxy-params.conf) @@ -104,6 +115,7 @@ server { add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com; connect-src 'self';" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; } # Security headers @@ -111,4 +123,5 @@ server { add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com; connect-src 'self';" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; }