security: Härtung der CMS-API + Deployment

App-Level:
- Security-Header (secureHeaders) global; /images/* mit strikter CSP+sandbox
  → bösartiges SVG kann kein JS im Origin ausführen
- Body-Limit 256 KB auf /api/*; Login-Rate-Limit (10/5min) gegen Brute-Force
- Upload: 8-MB-Limit + Format-Verifikation (sharp-Metadaten, SVG/GIF-Signatur)
- Comment-Längenlimit (10k) gegen DB-Bloat
- DB-Fehler nicht mehr roh ausliefern (serverError-Helper)
- Profil-PUT koalesziert Hugo-Builds (kein Build-Sturm)

Infra:
- Container läuft non-root (USER node, uid 1000) + Proxmox-Repo-chown
- Ports binden per Default auf 127.0.0.1 (BIND_ADDR-Escape-Hatch)
- Kong-CORS auf SITE_URL beschränkt statt "*"
- README: Härtungs- + Migrationshinweise

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 22:05:57 +02:00
parent 6d20be036a
commit 2650913050
13 changed files with 222 additions and 22 deletions
+37 -2
View File
@@ -72,8 +72,11 @@ Fragt interaktiv nur Storage/Bridge/IP ab (Enter = Default). Kein Token nötig.
2. `POSTGRES_PASSWORD` + `JWT_SECRET` setzen: je `openssl rand -hex 32`
3. Keys ableiten: `node scripts/generate-keys.mjs``ANON_KEY` + `SERVICE_ROLE_KEY` in `.env`
4. `SITE_URL` + `API_EXTERNAL_URL` auf die LAN-/Domain-Adresse setzen
5. `docker compose up -d --build` (Erststart: DB bootet + Schema/Migrations)
6. Login-User anlegen (Self-Signup ist aus):
5. `kong.yml`: Platzhalter `__CORS_ORIGIN__` durch `SITE_URL` (Browser-Origin) ersetzen
6. `BIND_ADDR` in `.env`: `127.0.0.1` hinter Reverse-Proxy (Standard), `0.0.0.0` für LAN-Direktzugriff
7. Repo dem Container-User (uid 1000) übereignen: `chown -R 1000:1000 <repo-root>`
8. `docker compose up -d --build` (Erststart: DB bootet + Schema/Migrations)
9. Login-User anlegen (Self-Signup ist aus):
```
source .env
curl -X POST "$API_EXTERNAL_URL/auth/v1/admin/users" \
@@ -89,6 +92,38 @@ Dann: Admin `…:8080/admin/` · Live `…:8080/` · Preview `…:8080/_preview/
`cd admin && npm install && npm run dev` (Vite-Devserver, proxyt `/api` +
`/_preview` an den laufenden Container auf :8080).
## Sicherheit / Härtung
Eingebaute Schutzmaßnahmen (Stand: Härtungs-Pass):
- **Sicherheits-Header** auf allen Antworten (X-Frame-Options, nosniff,
Referrer-Policy, HSTS); Uploads unter `/images/*` mit strikter CSP +
`sandbox` → ein bösartiges SVG kann kein JavaScript im Origin ausführen.
- **Rate-Limit** auf `/api/auth/login` (10 Versuche/IP pro 5 Min) gegen Brute-Force.
- **Body-Limit** 256 KB auf JSON-`/api/*`, Bild-Upload max. 8 MB mit
Format-Verifikation (sharp-Metadaten bzw. SVG/GIF-Signatur).
- **Comment-Limits** (Body ≤ 10 000 Zeichen) gegen DB-Bloat.
- **Kein Info-Leak**: rohe DB-Fehler werden serverseitig geloggt, nach außen
nur generische Meldungen.
- **Non-root**: der CMS-Container läuft als `node` (uid 1000).
- **Port-Binding** über `BIND_ADDR` (Standard `127.0.0.1`), DB nur auf localhost.
- **CORS** am Kong-Gateway auf die eigene `SITE_URL`-Origin beschränkt (kein `*`).
### Migration eines bestehenden Containers
Bei `git pull` auf einer schon laufenden Instanz greifen drei Änderungen, die
sonst einen frischen Deploy voraussetzen — **vor** dem nächsten
`docker compose up -d --build` von Hand nachziehen:
1. **Non-root:** `chown -R 1000:1000 <repo-root>` — sonst kann Hugo `public/`
nicht mehr bauen (Permission denied).
2. **CORS:** `kong.yml` enthält jetzt `__CORS_ORIGIN__`; auf einem bereits
initialisierten Container ersetzt das Proxmox-Script den Platzhalter nicht.
Manuell auf die `SITE_URL` setzen, sonst werden alle Browser-API-Calls
(inkl. Login) per CORS geblockt.
3. **BIND_ADDR:** Key in `.env` ergänzen. Default `127.0.0.1` ist hinter einem
TLS-Proxy korrekt; für LAN-Direktzugriff `0.0.0.0` setzen.
## API
Alle `/api/*` (ausser `/health`) verlangen `Authorization: Bearer <supabase-token>`.