cms.c2sgmbh/docs/anleitungen/SECURITY.md
Martin Porwoll 63b97c14f2 feat(security): enhance CSRF, IP allowlist, and rate limiter with strict production checks
- CSRF: Require CSRF_SECRET in production, throw error on missing secret
- IP Allowlist: TRUST_PROXY must be explicitly set to 'true' for proxy headers
- Rate Limiter: Add proper proxy trust handling for client IP detection
- Login: Add browser form redirect support with safe URL validation
- Add custom admin login page with styled form
- Update CLAUDE.md with TRUST_PROXY documentation
- Update tests for new security behavior

BREAKING: Server will not start in production without CSRF_SECRET or PAYLOAD_SECRET

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 05:06:15 +00:00

313 lines
8.3 KiB
Markdown

# Security-Richtlinien - Payload CMS Multi-Tenant
> Letzte Aktualisierung: 17.12.2025
## Übersicht
Dieses Dokument beschreibt die implementierten Sicherheitsmaßnahmen für das Payload CMS Multi-Tenant-Projekt.
---
## Security-Module
Alle Security-Funktionen befinden sich in `src/lib/security/`:
| Modul | Datei | Zweck |
|-------|-------|-------|
| Rate Limiter | `rate-limiter.ts` | Schutz vor API-Missbrauch |
| IP Allowlist | `ip-allowlist.ts` | IP-basierte Zugriffskontrolle |
| CSRF Protection | `csrf.ts` | Cross-Site Request Forgery Schutz |
| Data Masking | `data-masking.ts` | Sensitive Daten in Logs maskieren |
### Rate Limiter
**Vordefinierte Limiter:**
| Name | Limit | Fenster | Verwendung |
|------|-------|---------|------------|
| `publicApiLimiter` | 60 Requests | 1 Minute | Öffentliche API-Endpunkte |
| `authLimiter` | 5 Requests | 15 Minuten | Login-Versuche |
| `emailLimiter` | 10 Requests | 1 Minute | E-Mail-Versand |
| `searchLimiter` | 30 Requests | 1 Minute | Suche & Posts-API |
| `formLimiter` | 5 Requests | 10 Minuten | Formular-Submissions |
**Verwendung:**
```typescript
import { searchLimiter, rateLimitHeaders } from '@/lib/security'
export async function GET(req: NextRequest) {
const ip = getClientIp(req.headers)
const rateLimit = await searchLimiter.check(ip)
if (!rateLimit.allowed) {
return NextResponse.json(
{ error: 'Too many requests' },
{
status: 429,
headers: rateLimitHeaders(rateLimit, 30)
}
)
}
// ...
}
```
**Redis-Support:**
- Automatischer Fallback auf In-Memory-Store wenn Redis nicht verfügbar
- Für verteilte Systeme: Redis via `REDIS_URL` konfigurieren
### IP Allowlist/Blocklist
> **WICHTIG: TRUST_PROXY Konfiguration**
>
> Wenn die Anwendung hinter einem Reverse-Proxy (Caddy, Nginx, etc.) läuft,
> **muss** `TRUST_PROXY=true` gesetzt werden. Ohne diese Einstellung werden
> `X-Forwarded-For` und `X-Real-IP` Header ignoriert, um IP-Spoofing zu verhindern.
>
> ```bash
> # In .env setzen wenn hinter Reverse-Proxy:
> TRUST_PROXY=true
> ```
>
> Ohne `TRUST_PROXY=true` wird für alle Requests `direct-connection` als IP verwendet,
> was Rate-Limiting und IP-Allowlists/-Blocklists ineffektiv macht.
**Environment-Variablen:**
| Variable | Zweck | Format |
|----------|-------|--------|
| `TRUST_PROXY` | Proxy-Header vertrauen | `true` oder leer |
| `BLOCKED_IPS` | Globale Blocklist | IP, CIDR, Wildcard |
| `SEND_EMAIL_ALLOWED_IPS` | E-Mail-Endpoint Allowlist | IP, CIDR, Wildcard |
| `ADMIN_ALLOWED_IPS` | Admin-Panel Allowlist | IP, CIDR, Wildcard |
| `WEBHOOK_ALLOWED_IPS` | Webhook-Endpoint Allowlist | IP, CIDR, Wildcard |
**Unterstützte Formate:**
```bash
# Einzelne IP
BLOCKED_IPS=192.168.1.100
# Mehrere IPs
BLOCKED_IPS=192.168.1.100,10.0.0.50
# CIDR-Notation
BLOCKED_IPS=10.0.0.0/24
# Wildcard
BLOCKED_IPS=192.168.*
# Kombiniert
BLOCKED_IPS=10.0.0.0/8,192.168.1.50,172.16.*
```
**Verwendung:**
```typescript
import { validateIpAccess } from '@/lib/security'
const ipCheck = validateIpAccess(req, 'sendEmail')
if (!ipCheck.allowed) {
return NextResponse.json(
{ error: 'Access denied', reason: ipCheck.reason },
{ status: 403 }
)
}
```
### CSRF Protection
**Pattern:** Double Submit Cookie
**Token-Endpoint:** `GET /api/csrf-token`
> **WICHTIG: CSRF_SECRET in Production**
>
> In Production **muss** entweder `CSRF_SECRET` oder `PAYLOAD_SECRET` konfiguriert sein.
> Ohne Secret startet der Server nicht. Ein vorhersagbares Secret würde Angreifern
> erlauben, gültige CSRF-Tokens offline zu generieren.
>
> ```bash
> # In .env setzen:
> CSRF_SECRET=mindestens-32-zeichen-langes-geheimnis
> # ODER PAYLOAD_SECRET wird als Fallback verwendet
> ```
**Frontend-Integration:**
```typescript
// 1. Token abrufen
const res = await fetch('/api/csrf-token', { credentials: 'include' })
const { csrfToken } = await res.json()
// 2. Bei Mutations mitschicken
await fetch('/api/send-email', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'x-csrf-token': csrfToken,
},
body: JSON.stringify({ /* ... */ })
})
```
**Server-seitige Validierung:**
```typescript
import { validateCsrf } from '@/lib/security'
const csrf = validateCsrf(req)
if (!csrf.valid) {
return NextResponse.json(
{ error: 'CSRF validation failed' },
{ status: 403 }
)
}
```
**Bypass für Server-to-Server:**
- Requests ohne `Origin`/`Referer` Header werden als Server-to-Server behandelt
- API-Key-basierte Authentifizierung umgeht CSRF-Check
### Data Masking
**Automatisch maskierte Felder:**
- password, passwd, pwd
- token, accessToken, refreshToken
- apiKey, api_key
- secret, clientSecret
- credentials
- privateKey, private_key
- smtpPassword, smtp_pass
**Verwendung:**
```typescript
import { maskObject, createSafeLogger } from '@/lib/security'
// Objekte maskieren
const safeData = maskObject(sensitiveObject)
// Safe Logger verwenden
const logger = createSafeLogger('MyModule')
logger.info('User action', { password: 'secret' })
// Output: { password: '[REDACTED]' }
```
**Pattern-Erkennung:**
- JWT Tokens: Header bleibt, Payload/Signature maskiert
- Connection Strings: Passwort maskiert
- Private Keys: Komplett ersetzt
---
## Pre-Commit Hook
Secret Detection bei jedem Commit via `scripts/detect-secrets.sh`:
**Installation:**
```bash
ln -sf ../../scripts/detect-secrets.sh .git/hooks/pre-commit
```
**Erkannte Patterns:**
- API Keys und Tokens
- AWS Credentials
- Private Keys
- Passwörter in Code
- SMTP Credentials
- Database Connection Strings
- JWT Tokens
- Webhook URLs (Slack, Discord)
- GitHub/SendGrid/Stripe Tokens
**Ignorierte Dateien:**
- `*.min.js`, `*.min.css`
- `package-lock.json`, `pnpm-lock.yaml`
- `*.md`, `*.txt`
- `*.example`, `*.sample`
- `*.spec.ts`, `*.test.ts` (Test-Dateien)
---
## CI/CD Security
**.github/workflows/security.yml:**
| Job | Prüfung |
|-----|---------|
| `secrets` | Gitleaks Secret Scanning |
| `dependencies` | npm audit, Dependency Check |
| `codeql` | Static Code Analysis |
| `security-tests` | 177 Security Unit & Integration Tests |
---
## Test Suite
**Ausführung:**
```bash
# Alle Security-Tests
pnpm test:security
# Nur Unit-Tests
pnpm test:unit
```
**Abdeckung:**
| Test-Datei | Tests | Bereich |
|------------|-------|---------|
| `rate-limiter.unit.spec.ts` | 24 | Limiter, Tracking, Reset, TRUST_PROXY |
| `csrf.unit.spec.ts` | 34 | Token, Validierung, Origin |
| `ip-allowlist.unit.spec.ts` | 35 | CIDR, Wildcards, Endpoints, TRUST_PROXY |
| `data-masking.unit.spec.ts` | 41 | Felder, Patterns, Rekursion |
| `security-api.int.spec.ts` | 33 | API-Integration |
---
## Empfehlungen
### Production Checklist
- [ ] **`TRUST_PROXY=true`** setzen (Pflicht hinter Reverse-Proxy wie Caddy)
- [ ] **`CSRF_SECRET`** oder **`PAYLOAD_SECRET`** setzen (Server startet nicht ohne)
- [ ] Alle `BLOCKED_IPS` für bekannte Angreifer setzen
- [ ] `SEND_EMAIL_ALLOWED_IPS` auf vertrauenswürdige IPs beschränken
- [ ] `ADMIN_ALLOWED_IPS` auf Office/VPN-IPs setzen
- [ ] Redis für verteiltes Rate Limiting konfigurieren
- [ ] Pre-Commit Hook aktivieren
### Monitoring
- Rate Limit Violations loggen (429 Responses)
- CSRF Validation Failures überwachen
- Blocked IP Attempts tracken
- Login-Fehlversuche (authLimiter) alertieren
---
## Custom Login Route
Das Admin Panel verwendet eine Custom Login Route (`src/app/(payload)/api/users/login/route.ts`) mit folgenden Features:
- **Audit-Logging:** Jeder Login-Versuch wird in AuditLogs protokolliert
- **Rate-Limiting:** 5 Versuche pro 15 Minuten (authLimiter)
- **Content-Type Support:**
- JSON (`application/json`)
- FormData mit `_payload` JSON-Feld (Payload Admin Panel Format)
- Standard FormData (`multipart/form-data`)
- URL-encoded (`application/x-www-form-urlencoded`)
**Sicherheitsaspekte:**
- Passwort wird nie in Logs/Responses exponiert
- Fehlgeschlagene Login-Versuche werden mit IP und User-Agent geloggt
- Rate-Limiting verhindert Brute-Force-Angriffe
---
## Änderungshistorie
| Datum | Änderung |
|-------|----------|
| 17.12.2025 | **Security-Audit Fixes:** TRUST_PROXY für IP-Header-Spoofing, CSRF_SECRET Pflicht in Production, IP-Allowlist Startup-Warnungen, Tests auf 177 erweitert |
| 09.12.2025 | Custom Login Route Dokumentation, multipart/form-data _payload Support |
| 08.12.2025 | Security Test Suite (143 Tests) |
| 07.12.2025 | Rate Limiter, CSRF, IP Allowlist, Data Masking |
| 07.12.2025 | Pre-Commit Hook, GitHub Actions Workflow |