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 |
