Multi-Agent-Review: Race Conditions, DB-Indizes, TLS/Security-Härtung
CI / backend-tests (push) Failing after 19s
CI / frontend-build (push) Successful in 25s

Konsolidierte Funde aus postgres-/sql-/jwt-/owasp-top10-expert-Review:

- Race Conditions gefixt: doppelte aktive Kontrolle (SAVEPOINT + partieller
  Unique-Index), doppelte Mindermengen-Genehmigung (FOR UPDATE + Unique-Index),
  Lost-Update bei Nachfüllung (FOR UPDATE auf Fehlbestand/Objektposition).
- Migration 0007: partielle Unique-Indizes als DB-Sicherheitsnetz + fehlende
  FK-Indizes (fehlbestand.material_id, kontrolle(objekt_id,status),
  zustaendigkeit, benutzer_rolle.rolle, objektposition.ablaufdatum u.a.).
- Connection-Pool explizit begrenzt (pool_size=5, max_overflow=5) - ohne das
  könnte jeder uvicorn-Worker den Postgres max_connections-Wert sprengen.
- Timing-Angriff bei Login-Enumeration gefixt (konstante Antwortzeit über
  Dummy-Hash), JWT-Decode verlangt jetzt exp/sub-Claims.
- App-seitiges Rate-Limiting (slowapi, 5/min) auf /auth/login als Verteidigung
  in der Tiefe zusätzlich zum nginx-Limit.
- nginx: TLS mit selbstsigniertem Zertifikat (LAN-Betrieb, keine Domain),
  HSTS, Content-Security-Policy, Permissions-Policy ergänzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-04 18:47:06 +02:00
co-authored by Claude Sonnet 5
parent 0909dacf2b
commit ceddeb1bf4
15 changed files with 391 additions and 19 deletions
+17 -3
View File
@@ -63,7 +63,19 @@ PGCONF
grep -q "conf.d/\*.conf" "${PG_CONF_DIR}/postgresql.conf" || \
echo "include_dir = 'conf.d'" >> "${PG_CONF_DIR}/postgresql.conf"
echo "== nginx Reverse-Proxy Config (inkl. Security-Header) =="
echo "== TLS: selbstsigniertes Zertifikat (LAN-Betrieb ohne Domain, Karte owasp-Review) =="
mkdir -p /etc/nginx/ssl
if [ ! -f /etc/nginx/ssl/mabea.crt ]; then
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/mabea.key -out /etc/nginx/ssl/mabea.crt \
-subj "/CN=${APP_DOMAIN}"
chmod 600 /etc/nginx/ssl/mabea.key
echo "Selbstsigniertes Zertifikat erzeugt (10 Jahre gültig) - Browser warnen beim ersten Zugriff, bis manuell akzeptiert/importiert."
else
echo "Zertifikat existiert bereits, überspringe."
fi
echo "== nginx Reverse-Proxy Config (inkl. Security-Header, TLS) =="
sed -e "s/__APP_DOMAIN__/${APP_DOMAIN}/g" \
"$(dirname "$0")/nginx_mabea.conf.template" > /etc/nginx/sites-available/mabea.conf
ln -sf /etc/nginx/sites-available/mabea.conf /etc/nginx/sites-enabled/mabea.conf
@@ -94,5 +106,7 @@ echo "Zugangsdaten liegen (chmod 600) in ${CRED_FILE} NICHT auf stdout ausge
echo "DATABASE_URL von dort in backend/.env auf dem Server übernehmen (siehe backend/example.env)."
echo "Backend selbst ist noch nicht deployt siehe deploy/mabea-backend.service.template für den späteren systemd-Dienst."
echo
echo "Hinweis: nginx läuft aktuell nur über Port 80 (kein TLS). Für echten Betrieb vor Domain-Vergabe"
echo "TLS nachziehen (Let's Encrypt bei Domain, sonst selbstsigniertes Zertifikat fürs LAN)."
echo "nginx läuft über HTTPS mit selbstsigniertem Zertifikat (Port 80 leitet auf 443 um)."
echo "Browser warnen beim ersten Zugriff - Zertifikat manuell akzeptieren/importieren."
echo "Sobald eine echte Domain feststeht: ssl_certificate/-key in /etc/nginx/sites-available/mabea.conf"
echo "auf ein Let's-Encrypt-Zertifikat umstellen (certbot)."
+24 -3
View File
@@ -1,18 +1,39 @@
# limit_req_zone gehört in den http-Kontext (hier: via sites-enabled-Include-Kette
# bereits innerhalb von http{}, aber außerhalb des server{}-Blocks nötig).
# Grober Schutz gegen Brute-Force auf Login, ergänzt (nicht ersetzt) die
# Rate-Begrenzung/Lockout auf Anwendungsebene, die noch aussteht (Review-Fund).
# App-seitige Rate-Begrenzung (slowapi, siehe backend/app/core/rate_limit.py).
limit_req_zone $binary_remote_addr zone=mabea_login:10m rate=5r/m;
# HTTP -> HTTPS Redirect. Selbstsigniertes Zertifikat (Karte owasp-Review: LAN-
# Betrieb ohne öffentliche Domain, kein Let's Encrypt möglich) - Browser zeigen
# beim ersten Zugriff eine Warnung, bis das Zertifikat manuell akzeptiert wird.
server {
listen 80;
server_name __APP_DOMAIN__;
return 301 https://$host$request_uri;
}
# Basis-Hardening-Header (Review-Fund owasp-top10-expert). Sobald TLS aktiv ist,
# zusätzlich Strict-Transport-Security ergänzen.
server {
listen 443 ssl;
server_name __APP_DOMAIN__;
ssl_certificate /etc/nginx/ssl/mabea.crt;
ssl_certificate_key /etc/nginx/ssl/mabea.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Basis-Hardening-Header (Review-Fund owasp-top10-expert).
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
# CSP erlaubt nur same-origin (PWA lädt keine externen Skripte/Fonts) plus
# Kamera-Zugriff für den Barcode-Scanner (Karte 10). connect-src 'self'
# deckt auch API-Aufrufe ab, da Frontend/API dieselbe Origin teilen (siehe
# location-Blöcke unten). Owasp-Review-Fund: CSP fehlte komplett.
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self'; frame-ancestors 'none'" always;
add_header Permissions-Policy "camera=(self)" always;
location /api/v1/auth/login {
limit_req zone=mabea_login burst=5 nodelay;