Commit 354dfd

2026-03-17 16:47:29 Claude (MCP): [mcp] Add rate limiting and security headers plan
/dev/null .. Plans/Rate_Limiting_And_Security_Headers.md
@@ 0,0 1,70 @@
+ ---
+ category: spec
+ tags: [security, caddy, rate-limiting, headers, plan]
+ last_updated: 2026-03-17
+ confidence: high
+ ---
+
+ # Rate Limiting and Security Headers Plan
+
+ Both handled in Caddy on proxy-1. Two phases: security headers (immediate, zero risk) and rate limiting (requires xcaddy rebuild for the rate_limit module).
+
+ ## Phase 1: Security Headers (no module change)
+
+ Add to both `robot.wtf` and `*.robot.wtf` site blocks:
+
+ ```caddyfile
+ header {
+ Strict-Transport-Security "max-age=31536000; includeSubDomains"
+ X-Content-Type-Options "nosniff"
+ X-Frame-Options "SAMEORIGIN"
+ -Server
+ }
+ ```
+
+ Omitting CSP (requires script audit), Referrer-Policy, Permissions-Policy (low priority). No `preload` on HSTS (permanent, hard to undo).
+
+ ## Phase 2: Rate Limiting (requires xcaddy)
+
+ ### The problem
+
+ Standard Caddy Debian package has no rate limiting module. Need to build with `xcaddy build --with github.com/mholt/caddy-ratelimit`.
+
+ ### Rate limits by endpoint
+
+ | Endpoint | Limit | Rationale |
+ |---|---|---|
+ | `POST /auth/login` | 5/min per IP | Triggers external ATProto resolution |
+ | `POST /auth/signup` | 3/min per IP | Creates user records |
+ | `GET /auth/*` (general) | 60/min per IP | Mostly redirects/JSON |
+ | MCP OAuth (`/authorize*`, `/token`, `/register`) | 5/min per IP | Consent logic |
+ | MCP tool calls (`/mcp/*`) | 60/min per IP | AI agent bursts (30-60 calls/conversation) |
+ | Wiki writes (`POST */save`, `*/attachments*`) | 20/min per IP | Content modification |
+ | Wiki reads | No explicit limit (200/min if needed) | Low risk, cacheable |
+
+ All per-IP using `{remote_host}`. User-level limits deferred to application layer.
+
+ ### MCP considerations
+
+ - SSE connections are long-lived — don't rate limit `/mcp` itself (the stream endpoint)
+ - Rate limit `/mcp/*` (tool call messages) at 60/min — one AI conversation can make 30-60 calls
+ - MCP OAuth flow gets auth-level limits (5/min)
+
+ ## Implementation sequence
+
+ 1. **Phase 1 (immediate):** Add header blocks to Caddyfile, reload. Verify with `curl -I`.
+ 2. **Phase 2a:** Build xcaddy binary with rate_limit module, add to proxy Ansible repo.
+ 3. **Phase 2b:** Add rate_limit directives to Caddyfile template, deploy to proxy-1.
+ 4. **Validate:** `caddy list-modules` confirms module; curl loops verify 429s after threshold.
+
+ ## Rollback
+
+ - Headers: remove block, reload. Strictly additive, minimal risk.
+ - Rate limits: remove directives or raise `events` values, reload. No restart needed.
+ - Monitor with `journalctl -u caddy -f | grep '" 429 '` after deployment.
+
+ ## Important notes
+
+ - Caddy config is on proxy-1, managed by a **separate Ansible repo** (not robot.wtf)
+ - proxy-1 is the outermost proxy — `{remote_host}` is the real client IP
+ - `rate_limit` zone names must be globally unique across the Caddyfile
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9