docs: add post-pentest security standards to CLAUDE.md

Mandatory rules for all new code based on 33 pentest findings.
Covers: input validation, auth checks, SSRF prevention, session
management, CSP, nginx config, container security, RBAC.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-19 16:04:21 +00:00
parent 6e8a618cbc
commit 851d8001d6

View File

@@ -261,6 +261,50 @@ sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168
Single source of truth: `neode-ui/public/assets/img/app-icons/`
Naming: `{app-id}.{png|webp|svg}` — do not duplicate elsewhere.
## Security Standards (Post-Pentest — Mandatory)
These rules come from a full penetration test (33 findings, all remediated). Follow them for ALL new code.
### Backend (Rust)
- **Backend binds to 127.0.0.1 ONLY** — never `0.0.0.0`. All external access goes through nginx.
- **Validate ALL user input before path construction** — reject `..`, `/`, `\`, null bytes. Use the existing `validate_app_id()` pattern in `tor.rs`.
- **Never pass user input to `sudo` commands** — if unavoidable, validate strictly against an allowlist of characters `[a-zA-Z0-9_-]`.
- **Every HTTP endpoint that returns sensitive data MUST check authentication** — use `self.is_authenticated(&headers).await` or be in `UNAUTHENTICATED_METHODS` with justification.
- **Rate-limit authentication endpoints** — `extract_client_ip()` must only trust `X-Real-IP` from the loopback interface (127.0.0.1).
- **Federation messages require ed25519 signatures** — never accept unsigned peer-joined messages.
- **RBAC: use explicit allowlists, not prefix matching** — `method.starts_with("node.")` is BANNED. List exact methods per role.
- **Session cookies: `SameSite=Lax; HttpOnly; Path=/`** — `Strict` breaks iframe app fetches. `Lax` still prevents CSRF on POST.
- **Destructive operations require password re-verification** — factory reset, onboarding reset, identity export.
- **Remember-me secrets: use `OsRng` random bytes** — never derive from `/etc/machine-id` or other public data.
- **Rotate session tokens after privilege escalation** — TOTP verification must issue a new token, invalidating the pending one.
- **Tar archive extraction: validate every entry path** — never use `archive.unpack()`. Iterate entries and verify no `..` components or paths escaping the target directory.
### Frontend (Vue/TypeScript)
- **Validate redirect URLs** — use `isLocalRedirect()` from `router/index.ts` before any `window.location.href` assignment. Reject `javascript:`, protocol-relative (`//`), and external URLs.
- **Never use `v-html` with user input** — if unavoidable, always sanitize with `DOMPurify.sanitize()`.
- **CSP: no `unsafe-inline` in `script-src`** — Vite builds don't need it. Keep `unsafe-inline` only in `style-src` for Tailwind.
### Nginx
- **Session validation: `$cookie_session` (not `$cookie_session_id`)** — cookie name must match the Rust backend's `session=` cookie.
- **Prefer `auth_request` over cookie-presence checks** — `if ($cookie_session = "")` only checks presence, not validity. For sensitive endpoints, use nginx `auth_request` to validate against the backend.
- **All `/app/*` proxies are unauthenticated at nginx level** — each app must handle its own auth. Never expose apps with default credentials (change Grafana `admin/admin` on first boot, etc.).
### SSRF Prevention
- **Validate all user-supplied URLs** — require `https://` scheme, reject private IP ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1, fc00::/7).
- **Disable redirect following** — use `redirect(Policy::none())` on reqwest clients that fetch user-supplied URLs.
- **Onion addresses: validate v3 format** — exactly 56 base32 `[a-z2-7]` chars + `.onion`.
- **Webhook URLs: parse with `Url::parse`** — don't split on `:` for host extraction (breaks IPv6).
### Container Security
- **Memory limits on every container** — use `--memory=$(mem_limit <name>)` pattern from `first-boot-containers.sh`. Prevents one container from OOM-killing the system.
- **Health checks on every container** — define via `--health-cmd` in `podman run`.
- **User-stopped tracking** — when a user stops a container via UI, record in `user-stopped.json` so crash recovery and health monitor don't auto-restart it.
## Code Quality
- Zero compiler warnings (Rust and TypeScript)