From 3496cf0f263b64840dc75f9bf493520eace87640 Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Wed, 18 Mar 2026 20:03:07 +0800 Subject: [PATCH] Action performance audit findings - Add /health proxy block with rate limiting for external uptime monitoring - Fix Permissions-Policy on API responses: add passkey directives - Strengthen CSP: add frame-ancestors 'none' + upgrade-insecure-requests - Relax backend healthcheck interval from 10s to 30s (reduce overhead) Co-Authored-By: Claude Opus 4.6 (1M context) --- docker-compose.yaml | 2 +- frontend/nginx.conf | 13 +++++++++++-- frontend/proxy-params.conf | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index d369dd9..4201a2f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -41,7 +41,7 @@ services: - frontend_net healthcheck: test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8000/health')\""] - interval: 10s + interval: 30s timeout: 5s retries: 3 start_period: 30s diff --git a/frontend/nginx.conf b/frontend/nginx.conf index d7948ed..a80ec8f 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -12,6 +12,8 @@ limit_req_zone $binary_remote_addr zone=conn_search_limit:10m rate=10r/m; limit_req_zone $binary_remote_addr zone=conn_request_limit:10m rate=3r/m; # Event creation — recurrence amplification means 1 POST = up to 90-365 child rows limit_req_zone $binary_remote_addr zone=event_create_limit:10m rate=30r/m; +# Health endpoint — lightweight but rate-limited for resilience +limit_req_zone $binary_remote_addr zone=health_limit:1m rate=30r/m; # Use X-Forwarded-Proto from upstream proxy when present, fall back to $scheme for direct access map $http_x_forwarded_proto $forwarded_proto { @@ -164,6 +166,13 @@ server { include /etc/nginx/proxy-params.conf; } + # Health endpoint — proxied to backend for external uptime monitoring + location = /health { + limit_req zone=health_limit burst=5 nodelay; + limit_req_status 429; + include /etc/nginx/proxy-params.conf; + } + # API proxy (catch-all for non-rate-limited endpoints) location /api { proxy_set_header Upgrade $http_upgrade; @@ -184,7 +193,7 @@ server { add_header X-Frame-Options "SAMEORIGIN" always; 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 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'; frame-ancestors 'none'; upgrade-insecure-requests;" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; } @@ -192,7 +201,7 @@ server { add_header X-Frame-Options "SAMEORIGIN" always; 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 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'; frame-ancestors 'none'; upgrade-insecure-requests;" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # PT-I03: Restrict unnecessary browser APIs add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=(), publickey-credentials-get=(self), publickey-credentials-create=(self)" always; diff --git a/frontend/proxy-params.conf b/frontend/proxy-params.conf index 7ff2efb..711d631 100644 --- a/frontend/proxy-params.conf +++ b/frontend/proxy-params.conf @@ -11,6 +11,6 @@ add_header Cache-Control "no-store, no-cache, must-revalidate" always; add_header X-Frame-Options "SAMEORIGIN" always; 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 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'; frame-ancestors 'none'; upgrade-insecure-requests;" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; -add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always; +add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=(), publickey-credentials-get=(self), publickey-credentials-create=(self)" always;