feat: Phase 6 — nginx security headers, CSP hardening, rate limiting
- CSP: removed unsafe-eval, tightened frame-src to self + host ports, added frame-ancestors, base-uri, form-action directives - X-Frame-Options: SAMEORIGIN added after proxy_hide_header on all app proxies - HSTS: max-age=31536000; includeSubDomains on all server blocks - Rate limiting: 20r/s on /rpc/ with burst=40, 3r/s auth zone - Added X-DNS-Prefetch-Control, Permissions-Policy payment=() header Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
12
loop/plan.md
12
loop/plan.md
@@ -484,27 +484,27 @@
|
||||
> protect users. We add headers that prevent clickjacking, content type confusion, and XSS. We also
|
||||
> add rate limiting so attackers can't overwhelm the server with requests.
|
||||
|
||||
- [ ] **Fix Content Security Policy**: In `image-recipe/configs/nginx-archipelago.conf`, find line ~14 with the existing CSP. Replace the CSP header with a strict version:
|
||||
- [x] **Fix Content Security Policy**: In `image-recipe/configs/nginx-archipelago.conf`, find line ~14 with the existing CSP. Replace the CSP header with a strict version:
|
||||
```nginx
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:; frame-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
|
||||
```
|
||||
Note: `'unsafe-inline'` for styles is needed because Vue scoped styles sometimes inject inline styles. `'unsafe-eval'` is removed — if the app breaks, it means some JS is using `eval()` which should be fixed in code instead.
|
||||
Deploy the nginx config. Test the web UI thoroughly — if anything breaks, check browser console for CSP violations and adjust the policy minimally.
|
||||
|
||||
- [ ] **Replace X-Frame-Options stripping with SAMEORIGIN**: In `image-recipe/configs/snippets/archipelago-https-app-proxies.conf`, find all 38 occurrences of `proxy_hide_header X-Frame-Options;`. For each one, add after it:
|
||||
- [x] **Replace X-Frame-Options stripping with SAMEORIGIN**: In `image-recipe/configs/snippets/archipelago-https-app-proxies.conf`, find all 38 occurrences of `proxy_hide_header X-Frame-Options;`. For each one, add after it:
|
||||
```nginx
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
```
|
||||
This allows Archipelago's own UI to iframe apps but blocks external sites from framing them. Do the same in the HTTP config in `nginx-archipelago.conf`.
|
||||
Deploy and test: open an app in the Archipelago iframe — should still load.
|
||||
|
||||
- [ ] **Add HSTS header**: In `image-recipe/configs/nginx-archipelago.conf`, add to the HTTPS server block (or main server block if using HTTPS):
|
||||
- [x] **Add HSTS header**: In `image-recipe/configs/nginx-archipelago.conf`, add to the HTTPS server block (or main server block if using HTTPS):
|
||||
```nginx
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
```
|
||||
Note: Do NOT add `preload` — this is a local server, not a public domain.
|
||||
|
||||
- [ ] **Add rate limiting to RPC endpoint**: In `image-recipe/configs/nginx-archipelago.conf`, add at the top (before the `server` block):
|
||||
- [x] **Add rate limiting to RPC endpoint**: In `image-recipe/configs/nginx-archipelago.conf`, add at the top (before the `server` block):
|
||||
```nginx
|
||||
# Rate limit zones
|
||||
limit_req_zone $binary_remote_addr zone=rpc:10m rate=20r/s;
|
||||
@@ -518,7 +518,7 @@
|
||||
For auth-specific endpoints, apply stricter limits in the backend or add a separate location for auth RPCs.
|
||||
Deploy and test: normal UI use should work fine. Rapid-fire requests should get 429 responses.
|
||||
|
||||
- [ ] **Add remaining security headers**: In `image-recipe/configs/nginx-archipelago.conf`, add to the server block:
|
||||
- [x] **Add remaining security headers**: In `image-recipe/configs/nginx-archipelago.conf`, add to the server block:
|
||||
```nginx
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-DNS-Prefetch-Control "off" always;
|
||||
@@ -527,7 +527,7 @@
|
||||
```
|
||||
Deploy and verify: `curl -sI http://192.168.1.198 | grep -i "x-content\|referrer\|permissions\|strict-transport"`.
|
||||
|
||||
- [ ] **Verify Phase 6 — Nginx hardened**: Run these checks from another machine:
|
||||
- [x] **Verify Phase 6 — Nginx hardened**: Run these checks from another machine:
|
||||
1. `curl -sI http://192.168.1.198 | grep -i "content-security-policy"` — CSP header present, no `unsafe-eval`.
|
||||
2. `curl -sI http://192.168.1.198 | grep -i "x-content-type"` — `nosniff` present.
|
||||
3. `curl -sI http://192.168.1.198 | grep -i "x-frame-options"` — present on app proxies.
|
||||
|
||||
Reference in New Issue
Block a user