Commit 72a3de

2026-03-17 19:50:46 Claude (MCP): [mcp] Phase 1: expand security headers section with specific Caddy directives and CSP rationale
Plans/Rate_Limiting_And_Security_Headers.md ..
@@ 11,18 11,82 @@
## Phase 1: Security Headers (no module change)
- Add to both `robot.wtf` and `*.robot.wtf` site blocks:
+ ### Caddyfile snippet
+
+ Define a named snippet at the top of the Caddyfile (before any site blocks) and import it into the wiki site blocks. **Do not apply to the MCP site block** — MCP endpoints return JSON and the browser security headers are irrelevant there, and CSP in particular can cause unexpected behavior with SSE clients.
```caddyfile
- header {
- Strict-Transport-Security "max-age=31536000; includeSubDomains"
- X-Content-Type-Options "nosniff"
- X-Frame-Options "SAMEORIGIN"
- -Server
+ (security_headers) {
+ header {
+ # Remove server fingerprinting
+ -Server
+
+ # HSTS — 1 year. No preload (irreversible without HSTS preload list submission).
+ Strict-Transport-Security "max-age=31536000; includeSubDomains"
+
+ # Prevent MIME sniffing
+ X-Content-Type-Options "nosniff"
+
+ # Block clickjacking. Use SAMEORIGIN if iframing within the same site is needed.
+ X-Frame-Options "DENY"
+
+ # Disable legacy XSS filter (modern browsers; CSP is the real mitigation)
+ X-XSS-Protection "0"
+
+ # Referrer: send origin on same-site, origin-only on cross-site HTTPS
+ Referrer-Policy "strict-origin-when-cross-origin"
+
+ # Permissions policy: disable sensors/hardware not used by the wiki
+ Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
+
+ # CSP — see notes below for rationale
+ Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
+ }
}
```
- Omitting CSP (requires script audit), Referrer-Policy, Permissions-Policy (low priority). No `preload` on HSTS (permanent, hard to undo).
+ Apply it to the wiki site blocks:
+
+ ```caddyfile
+ # Otterwiki web UI
+ wiki.robot.wtf {
+ import security_headers
+ import wiki_auth
+ reverse_proxy 3gw.lan:8080
+ }
+
+ # Auth, management UI (if served separately)
+ *.robot.wtf {
+ import security_headers
+ # ... other directives
+ }
+ ```
+
+ **Do not import `security_headers` in the `mcp.robot.wtf` site block.**
+
+ ### CSP rationale
+
+ Otterwiki's frontend (audited from templates): all JS and CSS are self-hosted (`/static/`). No external CDNs. MathJax, Mermaid, Font Awesome, and Roboto fonts are all bundled. However:
+
+ - `layout.html` has inline `<script>` blocks (flash message rendering) — requires `'unsafe-inline'` for scripts.
+ - `editor.html` has an inline `<style>` block — requires `'unsafe-inline'` for styles.
+ - `editor.js` uses `preview_block.innerHTML = data.preview_content` to render server-fetched HTML into the DOM — this is XSS-safe as long as the server sanitizes, but means wiki content may include `<img>` tags with arbitrary `https:` src URLs (user-uploaded or markdown-linked images). Hence `img-src 'self' data: blob: https:`.
+ - Font CSS uses local `data:` URIs for IE compat woff, so `font-src 'self' data:` is needed.
+ - `frame-ancestors 'none'` enforces no-iframe (equivalent to `X-Frame-Options: DENY` for modern browsers; keep both for compatibility).
+
+ Dropping `'unsafe-inline'` from `script-src` would require nonce injection in the app — that's an application change, out of scope for Phase 1.
+
+ ### Verification
+
+ After reload, verify with:
+
+ ```bash
+ curl -sI https://wiki.robot.wtf/ | grep -E '(strict-transport|x-content|x-frame|referrer|permissions|content-security|x-xss)'
+ ```
+
+ Expected: all six response headers present. Confirm `Server:` header is absent.
+
+ Also open browser DevTools → Console on the wiki to confirm no CSP violations are logged.
## Phase 2: Rate Limiting (requires xcaddy)
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