Properties
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
/mcpitself (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
- Phase 1 (immediate): Add header blocks to Caddyfile, reload. Verify with
curl -I. - Phase 2a: Build xcaddy binary with rate_limit module, add to proxy Ansible repo.
- Phase 2b: Add rate_limit directives to Caddyfile template, deploy to proxy-1.
- Validate:
caddy list-modulesconfirms module; curl loops verify 429s after threshold.
Rollback
- Headers: remove block, reload. Strictly additive, minimal risk.
- Rate limits: remove directives or raise
eventsvalues, 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_limitzone names must be globally unique across the Caddyfile
