SEC-1c Plan-Review — Transport / Privilege / Operational Hardening

Projekt: Steve-TradingBot · Phase: SEC-1c · Author: claude-opus-4-7[1m]
Generated: 2026-05-13 10:46 UTC · master HEAD: 2fbc7b5 (SEC-1b-6 closed)
Status: NO CODE Plan-Review only — Operator-GO erforderlich vor SEC-1c-Code-Phase
Empfohlene Reihenfolge: SEC-1c-1 HTTPS → SEC-1c-2 Sessions → SEC-1c-3 DB-LP → SEC-1c-4 Admin-Rotation → CSP → Audit-Alerts → D Net-Seg → C SSH+A+B → E Secret-Rotation

Ziel: Nach SEC-1b-6 (Identity-Layer abgesichert) jetzt Transport-Sicherheit, Privilege-Separation, Operational-Security und Browser-Hardening abschließen, BEVOR MH-5b/c/d Mutation-Actions ausgeliefert werden. Wichtigste Operator-Pin: „DB-Least-Privilege ist kein optional nice-to-have mehr — nach dem DB-Wipe-Incident ist es ein struktureller Sicherheitsblocker für Production-Härtung.“


1 — Live-State-Check (Boundaries)

DomainItemCurrentRisk
Transportnginx server_name81.169.213.37 (IP-only)HIGH — blockt SEC-1d Passkey; HSTS sinnlos auf IP
TransportTLS certself-signed /root/steves-tradingbot/ssl/dashboard.crtHIGH — Browser-Trust-Warnung; kein Let's Encrypt installiert
TransportAPP_URLhttp://127.0.0.1:8090 plain HTTPHIGH — Filament generiert HTTP-Links; Secure-Cookies wirkungslos
Transportnginx proxy-targetproxy_pass http://127.0.0.1:8050 (Bot-Dashboard, 502 currently)MEDIUM — GUI auf :8090 ist nicht hinter nginx; nur lokal erreichbar
TransportBot-Dashboard exposure0.0.0.0:8050 (compose port)MEDIUM — öffentlich erreichbar ohne Auth-Layer
SessionSESSION_ENCRYPTfalseHIGH — Session-Payload Klartext in DB
SessionSESSION_SECURE_COOKIEnicht gesetztHIGH — Cookie ohne Secure-Flag
SessionSESSION_HTTP_ONLYnicht gesetzt (default true)MEDIUM — default ist sicher; explizit pinnen
SessionSESSION_SAME_SITEnicht gesetzt (Laravel-default: lax)MEDIUMstrict empfohlen
SessionTRUSTED_PROXIES / Middlewarenicht konfiguriert (bootstrap/app.php ohne trustProxies())HIGH — Laravel erkennt HTTPS-via-nginx falsch → Secure-Cookie verliert Secure-Flag
SessionSESSION_LIFETIME120 (Minuten)OK — Operator-Empfehlung 15–30 min für Admin-Panel
PrivilegeDB-Runtime-Usertradingbot_gui = SUPERUSER + CREATE ROLE + CREATE DB + REPLICATION + BYPASS RLSCRITICAL — Worst-Case; kompromittierter GUI-Prozess kann komplette DB löschen (genau der INCIDENT-Vektor)
PrivilegeMigration-User getrenntnein — gleicher UserHIGH — INCIDENT 2026-05-12 Vektor weiterhin offen
OpsAdmin-Useradmin@example.localMEDIUM — Default-User, technisch+organisatorisch Risiko
BrowserCSP-Headerkein Content-Security-Policy gesetztMEDIUM — XSS-/Clickjacking-Schutz fehlt
BrowserX-Frame-Optionsnicht gesetztMEDIUM
BrowserX-Content-Type-Optionsnur für /shares/ gesetzt (nosniff)MEDIUM — nicht für Admin-Panel-Route
NetworkGUI-Container port127.0.0.1:8090 → container:8000OK — bereits localhost-bound
NetworkGUI-DB portkein Host-Mapping (intern gui-db:5432)OK
NetworkDocker-Netzesteve-tradingbot_clawbot-net + bridge · GUI+DB im gleichen NetzOK — aber Segmentierung prüfen
NetworkBot-Dashboard 80500.0.0.0:8050MEDIUM — offen; sollte hinter nginx-auth oder localhost-only
Visibilityaudit_events-Tablevorhanden (RECON-MH-Pattern)OK — Hook-Punkte vorhanden, aber keine Alerts
VisibilityTelegram-Notifiervorhanden (Notifier-1)OK — nutzbar für Audit-Alerts
Hygienefail2banaktiv (sshd-jail)OK — SEC-1b-0 closure
HygieneSSHPasswordAuthenticationSEC-1b-0: prohibit-password für root; gen sshd ungeprüftMEDIUM — global no + AllowGroups prüfen
BoundariesBot PID (clawbot)290 unverändert (python3 main.py --paper)OK
BoundariesWorker PID (clawbot-worker)1 unverändertOK
BoundariesBINANCE_TESTNETtrueOK
Boundariescmd 13 / managed_proposalscancelled / 0 rowsOK

Kritischste Live-Findings

  1. DB-Runtime-User ist SUPERUSER — Worst-Case für Privilege-Separation. SEC-1c-3 ist post-INCIDENT der wichtigste strukturelle Block.
  2. Plain-HTTP + self-signed Cert + IP-only — HTTPS-Setup ist Voraussetzung für alles andere (Cookies, HSTS, Passkey-BACKLOG).
  3. Keine FQDN registriert — Operator-Decision erforderlich (Domain-Wahl blockt SEC-1d dauerhaft, wenn ungeklärt).
  4. TrustProxies-Middleware fehlt — muss zusammen mit HTTPS aktiviert werden, sonst entwertet sich SEC-1c-2 selbst.

2 — SEC-1c-1 HTTPS / Let's Encrypt HIGH

Production-Topology (Soll)

Internet
   |
   v
nginx :443 (Let's Encrypt cert)   ----+
   |                                    |
   +---> Admin GUI Filament @127.0.0.1:8090
   |
   +---> /shares/ static (unchanged)
   |
nginx :80  ---> force-redirect :443 (außer /.well-known/acme-challenge)

Sub-Items

  1. Domain-Wahl — Operator-Decision erforderlich:
  2. Let's Encrypt Setupcertbot + --nginx-Plugin ODER --standalone ODER --webroot:
  3. nginx-Config-Patch (Plan only):
  4. Laravel-Konfiguration:
  5. Rollback-Pfad: alte nginx-conf als .bak sichern; bei Cutover-Fehler < 60s zurückspielen.

Risiken

Aufwand

Domain-Setup (DNS A-Record) 5–30min, certbot 5min, nginx-config 30min, Test 15min. Σ 1–2h inkl. Rollback-Plan.

3 — SEC-1c-2 Session Hardening HIGH

Minimum-Set (.env)

SESSION_ENCRYPT=true
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=strict
SESSION_DOMAIN=<FQDN>             # exakt die Domain ohne Schema
TRUSTED_PROXIES=*                  # in unserem nginx-localhost-Pfad sicher

TrustProxies-Middleware (Plan only)

In gui/bootstrap/app.php im ->withMiddleware()-Block:

$middleware->trustProxies(at: '*', headers: \Illuminate\Http\Request::HEADER_X_FORWARDED_FOR
  | \Illuminate\Http\Request::HEADER_X_FORWARDED_HOST
  | \Illuminate\Http\Request::HEADER_X_FORWARDED_PORT
  | \Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO);

Begründung: nginx läuft lokal (localhost-only Proxy), at: '*' ist akzeptabel; bei externalisiertem Reverse-Proxy müssten wir die CIDR-Liste pinnen.

Session-Lifetime (Operator-Pin: 15–30 min Admin-Idle-Timeout)

Risiken

Aufwand

Σ 30–60min inkl. Test der Cookie-Flags + Re-Login.

4 — SEC-1c-3 DB Least-Privilege (2-User-Modell) CRITICAL

Aktueller Zustand (live verifiziert): tradingbot_gui ist Superuser, Create role, Create DB, Replication, Bypass RLS. Das ist exakt der Vektor, der den INCIDENT 2026-05-12 möglich gemacht hat.

Soll-Modell

RolleZweckPrivs erlaubtPrivs verboten
tradingbot_gui_app Runtime (Laravel im GUI-Container) CONNECT auf DB; USAGE auf public-Schema; SELECT/INSERT/UPDATE/DELETE auf alle Tables; USAGE/SELECT auf alle Sequences; EXECUTE auf Functions; TEMPORARY auf DB kein CREATE, kein DROP, kein ALTER; kein SUPERUSER/CREATE ROLE/CREATE DB/REPLICATION
tradingbot_gui_migrator Schema-Migrationen (php artisan migrate) CONNECT; USAGE/CREATE auf public; CREATE/ALTER/DROP TABLE/INDEX/SEQUENCE/FOREIGN KEY/CONSTRAINT; SELECT/INSERT/UPDATE/DELETE auf alle (für data-migrations) kein SUPERUSER/CREATE DB/CREATE ROLE/REPLICATION
tradingbot_gui (legacy) Owner / DBA-Notfall (von außen SSH+psql) SUPERUSER bleibt aber: nie in GUI-Container .env

Cutover-Sub-Plan

  1. pg_dump-Backup (durable rule) vor jedem Schritt.
  2. Neue Rollen anlegen via psql (kein Migration, keine Schema-Änderung).
  3. GRANTs explizit setzen + ALTER DEFAULT PRIVILEGES für zukünftige Tables.
  4. Test-Phase: GUI-Container weiterhin auf tradingbot_gui (SUPERUSER); parallel psql-Connect-Tests mit tradingbot_gui_app:
  5. Cutover: .env in GUI-Container wechseln DB_USERNAME=tradingbot_gui_appphp artisan config:clear → GUI-Container restart (oder DB::reconnect() via Artisan, je nach Decision).
  6. Migration-Pfad: scripts/migrate.sh ODER manueller Befehl DB_USERNAME=tradingbot_gui_migrator php artisan migrate ODER eigener Migration-Container.
  7. SUPERUSER-Lock (Final): ALTER USER tradingbot_gui WITH NOSUPERUSER NOCREATEROLE NOCREATEDB — nur noch für Notfall-Zugriff.

Decision-Punkt

Cutover-MethodProContra
(a) GUI-Container restart nach .env-EditSauber, eindeutiger Cut~10s Downtime für GUI; bestehende Sessions invalidiert (kombiniert sich mit SEC-1c-2 Re-Login — gut!)
(b) DB::reconnect() via Artisan ohne RestartKeine DowntimeBrittle: Filament-internes Connection-Pool bleibt evtl. auf altem User; PHP-FPM-Worker müssen alle reconnecten

Empfehlung: (a) — klar, atomic, kombiniert mit SEC-1c-2-Cutover sinnvoll.

Risiken

Aufwand

Σ 2–3h inkl. Test-Suite (manual SQL + 1 PHPUnit-Test „db-user-cannot-DROP“).

5 — SEC-1c-4 Admin Rotation HIGH

Plan

  1. Neuen Admin anlegen via Filament-CLI ODER seeder:
  2. Login + MFA-Enrollment mit echtem Authenticator-App; 8 Recovery-Codes sicher ablegen.
  3. Test-Phase: Login + Admin-Aktion (z. B. Resource lesen).
  4. Alten Admin deaktivieren (NICHT löschen — Audit-Trail erhalten):

    Empfehlung: (b) — passt zu Scope-Lock „klein/atomar“. Spalte is_active kann in BACKLOG SEC-1c-FU-1 nachgezogen werden.

Übergangsphase

VarianteProContra
(i) 2 Admins parallel während Test-PhaseLockout-Safety: alter Admin als BackupLängerer high-risk-Zustand
(ii) Hard-CutoverSchnell, klarWenn neuer Admin MFA-Setup verfehlt → Lockout, Break-Glass-Pfad nötig

Empfehlung: (i) — 2 Admins parallel; alten Admin erst deaktivieren nachdem neuer Admin vollständig (Login + MFA + Recovery-Codes gesichert) verifiziert wurde.

Aufwand

Σ 30min.

6 — SEC-1c-5 CSP / Security Headers (2-phasig) MEDIUM

Phase 1 — Safe-Defaults (gleichzeitig mit HTTPS-Cutover deploybar)

Header in nginx-Server-Block (HTTPS) — add_header mit always-Flag:

add_header Strict-Transport-Security "max-age=86400; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

Phase 2 — Moderate CSP (später, gestaffelt)

Content-Security-Policy:
  default-src 'self';
  img-src 'self' data:;
  style-src 'self' 'unsafe-inline';
  script-src 'self' 'unsafe-inline';
  font-src 'self' data:;
  connect-src 'self';
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';

Operator-Warnung: Filament nutzt Inline-Styles + Livewire-Inline-Scripts; strict-CSP würde die Admin-UI brechen. unsafe-inline bleibt zunächst, später nonce-basiert in BACKLOG SEC-1c-FU-2.

Risiken

Aufwand

Phase 1: 20min. Phase 2: 1–2h (Test + Adjust).

7 — SEC-1c-6 Audit-Alerts MEDIUM

Trigger

EventQuelleNotification-Severity
MFA Setup completedFilament SetUpAppAuthenticationAction-Hookinfo
MFA disabled / regeneratedFilament Disable+Regenerate-Actionswarning
MFA Break-Glass (DB-direkt-UPDATE)DB-Trigger ODER manueller Audit-Log-Eintragcritical
Failed login attemptFilament Login-throttleinfo (Aggregat alle 10)
3 fehlgeschlagene MFA-CodesFilament MFA-Step-Validatorwarning
Successful login from new IPsession create + last_login_ip-cachewarning
Successful login from new ASN/country+ GeoIP-Lookup (SEC-2 / BACKLOG)warning
Role escalation (UPDATE users.role)Eloquent observer ODER DB-Triggercritical
Command requested by viewer (denied)Filament canCreate/canEdit-checkswarning

Hook-Design

  1. Bestehende audit_events-Tabelle nutzen (RECON-MH-Pattern, schon im Einsatz).
  2. Eloquent-Observer auf User-Model für role-changes + MFA-changes.
  3. Filament-Action-Hook (after) für MFA-Setup/Disable/Regenerate.
  4. Telegram-Notifier (Notifier-1 bereits live) nutzt audit_event-Inserts als Trigger via Queued Job ODER Eloquent Observer.
  5. Rate-Limit: max 3 Alerts/Minute/Severity, sonst Aggregat-Mode.

Aufwand

Σ 3–4h inkl. Tests.

8 — A + B Defense-in-Depth (Session-Lifetime + nginx-Rate-Limits) MEDIUM

A. Session-Lifetime-Review

B. nginx Rate-Limits

# /etc/nginx/conf.d/limits.conf
limit_req_zone $binary_remote_addr zone=admin_login:10m rate=10r/m;
limit_conn_zone $binary_remote_addr zone=admin_conn:10m;

# in HTTPS server-block:
location /admin/login {
    limit_req zone=admin_login burst=5 nodelay;
    limit_conn admin_conn 3;
    proxy_pass http://127.0.0.1:8090;
    ...
}

Defense-in-Depth zu Laravel-Login-Throttle (MAX_LOGIN_ATTEMPTS=3); blockt L7-Floods bevor sie Laravel erreichen.

Aufwand

Σ 30min.

9 — C SSH-Härtung LOW

Aktueller Stand (SEC-1b-0)

Zusätzlich

Risiken

Aufwand

Σ 30min.

10 — D Docker Netzwerksegmentierung MEDIUM

Aktuell

Soll-Plan

  1. Prüfung: docker network inspect steve-tradingbot_clawbot-net → sind GUI + DB die einzigen Member?
  2. Bot-Dashboard 8050: 3 Optionen: Empfehlung: (a) als SEC-1c-Item; (c) als BACKLOG.
  3. Netzwerk-Audit: iptables -L + ss -tlnp auf Host — finden aller 0.0.0.0-Bindings; dokumentieren.

Aufwand

Σ 1h Prüfung + 30min Cutover Bot-Dashboard.

11 — E Secret-Rotation (mittelfristig) MEDIUM

Rotations-Liste (post-INCIDENT-Hygiene)

SecretRisiko bei RotationSchritte
APP_KEY HOCH — rotiert encryptet alle encrypted-cast-Spalten (MFA-Secret, Recovery-Codes, ggf. weitere) Eigene Sub-Phase mit APP_PREVIOUS_KEYS-Compat-Layer; pg_dump vorher; alle Admins reset MFA
DB-Passwort DB_PASSWORDMittel — GUI-Container muss reconnecten2-Step: neuer User mit neuem PW (siehe SEC-1c-3); altes PW dann ALTER USER ... PASSWORD
Telegram-Bot-TokenNiedrigNeuen Token erzeugen, alten widerrufen; Notifier testen
OpenAI-Key (falls genutzt)NiedrigIm OpenAI-Dashboard rotieren, neuen Key in .env setzen
SSH-Authorized-KeysMittel — Lockout-GefahrNeuen Key generieren, hinzufügen, alten nach Test-Login entfernen
Recovery-Codes (alle bestehenden)NiedrigFilament-Action „Regenerate Recovery Codes“; alte werden invalidiert

Empfehlung

Eigene Sub-Phase SEC-1c-7 als letztes Item. APP_KEY-Rotation ist riskant; sollte separat geplant werden mit eigener pg_dump-Vorab-Sicherung + Test-Run im Staging.

Aufwand

Σ 3–4h inkl. APP_KEY-Compat-Layer + Test-Cycle.

12 — Atomicity & Commit-Block-Plan

Vorschlag (operator-anpassbar)

#BlockItemsRestart-PflichtRisikoAufwand
1HTTPS + Sessions + Phase 1 Headers + B nginx-RLSEC-1c-1 + SEC-1c-2 + SEC-1c-5 Phase 1 + Bnginx-reload + GUI-Container-RestartHIGH (TLS-Cutover, Cookie-Cutover atomar)2–3h
2DB-Least-Privilege (2-User-Modell)SEC-1c-3GUI-Container-RestartHIGH (Connection-Cutover)2–3h
3Admin-RotationSEC-1c-4kein RestartMEDIUM (Lockout-Gefahr)30min
4Audit-Alerts + A Session-LifetimeSEC-1c-6 + Aconfig-clear, kein RestartLOW3–4h
5Phase 2 moderate CSPSEC-1c-5 Phase 2nginx-reloadMEDIUM (UI-Breakage-Risk)1–2h
6D Docker-NetzwerksegmentierungD + Bot-Dashboard localhost-bindcompose-up für Bot-DashboardLOW1–2h
7C SSH-HärtungCsshd-reloadLOW (lokaler Konsolen-Fallback bleibt)30min
8E Secret-Rotation (SEC-1c-7)EvariabelHIGH (APP_KEY)3–4h

Bot/Worker bleiben in allen 8 Blöcken unberührt. BINANCE_TESTNET=true bleibt. Bot-PID 290 + Worker-PID 1 unverändert pinnen.

Gesamt-Aufwand SEC-1c: 14–22h über 8 atomic commits, jeder mit eigenem pg_dump-Backup + Safe-Runner-Tests + Live-State-Verify.


13 — Operator-Decision-Points (Pflicht vor Block 1)

  1. Domain für GUI:
  2. DB-Cutover-Method (SEC-1c-3):
  3. Admin-Rotations-Übergang:
  4. Neue Admin-Email: konkreter Wert? (z. B. talk@kw-baustoffe.de oder steve@kw-baustoffe.de)
  5. Session-Lifetime: 30 min global ODER 30 min Admin-only + 120 min sonst?
  6. Bot-Dashboard 8050: localhost-bind + nginx-Auth davor (a) ODER löschen wenn ungenutzt (c)?
  7. HSTS-Strategie: max-age 1 Tag → 1 Jahr nach 1 Woche fehlerfrei (Empfehlung) ODER sofort 1 Jahr?
  8. CSP Phase 1 jetzt + Phase 2 später ODER beide zusammen?

14 — NO-GO-Bedingungen (durable, pro Block)


15 — Empfehlung & nächster Schritt

Empfehlung: Start mit Block 1 (HTTPS + Sessions + Phase 1 Headers + nginx-RL) sobald Operator-Decisions 1, 5, 7 beantwortet sind.

Parallel kann Block 2 (DB-Least-Privilege) in Test-Phase laufen (Rollen + GRANTs anlegen, Connect-Tests mit tradingbot_gui_app) ohne Cutover — Cutover dann nach Block 1.

Block 3 (Admin-Rotation) direkt nach Block 1, weil 1) HTTPS aktiv + Cookies sicher = neues Enrollment läuft über sicheren Channel, 2) Operator hat dann frischen Admin im neuen System.

Block 8 (Secret-Rotation, vor allem APP_KEY) ist letztes Item — nicht riskieren bevor alles andere stabil läuft.

Status

PLAN-REVIEW Plan komplett. Warte auf:

Kein Code geschrieben. Kein git / docker / test-Touch durchgeführt — pure analysis + scope-pin only.