6.1 KiB
6.1 KiB
06a – Auth-Audit (Security & Data-Engineering)
Projekt: CRM System v1.0
Datum: 2026-06-04
Auditor: Security Data Engineer (Phase 6)
Repository: Leopoldadmin/crm-system, Branch main
Scope: JWT-Implementation, Password-Hashing, Auth-Endpoints, CORS/CSP-Header, Token-Rotation, Secrets in Git-Verlauf
Findings
| ID | Severity | Kategorie | Beschreibung | Empfehlung |
|---|---|---|---|---|
| AUTH-01 | PASS | JWT-Algorithmus | HS256 mit python-jose[cryptography]==3.3.0 gemäß Architecture-Decision Section 13.1. decode_access_token() validiert Signatur und Ablauf korrekt über jwt.decode() mit explizitem Algorithmus-Parameter. |
Beibehalten. Für v1.2 RS256 evaluieren (bessere Rotation, kein Shared-Secret). |
| AUTH-02 | PASS | Secret-Länge | AUTH_SECRET wird in config.py mit Field(..., min_length=32) validiert. Ein benutzerdefinierter Validator validate_auth_secret() lehnt Platzhalter wie replace-me, changeme und den Literal secret ab. Hard-Fail bei fehlendem oder zu kurzem Secret (kein Fallback). |
Keine Änderung nötig. Erfüllt NFR-2 und Architecture R-5. |
| AUTH-03 | PASS | Token-Expiry | 24h über JWT_EXPIRY_HOURS in config.py konfigurierbar. create_access_token() setzt exp-Claim korrekt via datetime.now(UTC) + timedelta. decode_access_token() fängt JWTError ab (deckt auch Expiry). |
Kurzfristigeres Expiry (z.B. 2h) + Refresh-Token in v1.1 für höhere Sicherheit. |
| AUTH-04 | PASS | Token-Validation | get_current_user in deps.py nutzt decode_access_token(), prüft sub-Claim und User-Existenz (inkl. deleted_at IS NULL). 401-Response mit token_expired_or_invalid für konsistente Client-Behandlung. |
Validierung ist robust. Zusätzlicher Check auf iat-Claim könnte Replay-Angriffe erschweren (optional). |
| AUTH-05 | PASS | Password-Hashing | bcrypt via passlib[bcrypt]==1.7.4 mit bcrypt==4.0.1. pwd_context mit deprecated="auto". BCRYPT_ROUNDS=12 konfigurierbar. hash_password() und verify_password() korrekt implementiert. |
Keine Änderung nötig. bcrypt 4.0.1 ist aktuell und sicher. |
| AUTH-06 | PASS | Kein Default-Admin | Bootstrap-Registrierung via POST /api/v1/auth/register nur bei leerer users-Tabelle. Nach erstem User → 403 (BootstrapAlreadyCompleted). Kein admin/admin-Fallback. |
Erfüllt Architecture R-3. |
| AUTH-07 | PASS | Register-Endpoint | POST /api/v1/auth/register validiert via UserRegisterRequest: email: EmailStr, password: str(min_length=8, max_length=128), name: str(min_length=1, max_length=255). 409 bei doppelter Email (generisch), 201 bei Erfolg. |
Validiert korrekt. Rate-Limiting fehlt noch (v1.1, siehe Architecture 13.4). |
| AUTH-08 | PASS | Login-Endpoint | POST /api/v1/auth/login (OAuth2Form) + /login/json (JSON). 401 mit generischer Meldung "Invalid email or password" – leakt nicht, ob Email existiert. WWW-Authenticate: Bearer Header gesetzt. |
Erfüllt FR-1.2 Akzeptanzkriterien. |
| AUTH-09 | PASS | Logout-Endpoint | POST /api/v1/auth/logout validiert Token (Dependency get_current_user), aber keine serverseitige Blacklist. Client-seitiger Token-Discard dokumentiert. |
Für v1 akzeptabel. Server-seitige Blacklist erst in v1.1. |
| AUTH-10 | PASS | /users/me-Endpoint | GET /api/v1/users/me via get_current_user geschützt. 401 ohne Token, 401 mit expired Token (token_expired_or_invalid), password_hash nie im Response. |
Erfüllt FR-1.6 Akzeptanzkriterien AC#7-#9. |
| AUTH-11 | PASS | CORS-Whitelist | CORS_ORIGINS aus Env-Var (Komma-separiert), Default http://localhost:5500,http://localhost:8000. Kein *. settings.cors_origins_list parsed korrekt. |
Erfüllt Architecture R-4. |
| AUTH-12 | INFO | CSP-Header | In main.py security_headers_middleware gesetzt. Dev: script-src 'self' 'unsafe-inline' ... (für Alpine.js). Prod: nur script-src 'self' ... (ohne unsafe-inline) – Alpine.js-Inline-Skripte würden blockiert. X-Content-Type-Options, X-Frame-Options, HSTS (Prod) gesetzt. |
Vor Produktion: Nonce-basierte CSP für Alpine.js implementieren (v1.1 ToDo). Aktuelle Prod-CSP würde Frontend blockieren. |
| AUTH-13 | INFO | Refresh-Token-Rotation | /api/v1/auth/refresh existiert, re-signed aber nur mit gleichem Secret – keine echte Rotation. Rotation ist für v1.1 geplant und im Code-Kommentar dokumentiert. |
Kein Sicherheitsrisiko für v1, da Token-Expiry 24h beträgt. Für v1.1: Refresh-Token mit separatem Secret + Rotation. |
| AUTH-14 | WARN | Secrets im Git-Verlauf | git log -p zeigt Passwörter in Test-Dateien (test_auth.py, test_smoke.py, conftest_helper.py), z.B. "password": "Test1234!", "password": "SuperSecret123!". Dies sind Test-Credentials ohne Produktionsrelevanz. |
Kein kritisches Risiko, aber Good-Practice: Test-Passwörter aus Git-Verlauf entfernen (via git filter-branch oder git rebase). Kein Blocker für Phase 7. |
Summary: PASS ✅
Das Auth-System ist sicher und erfüllt alle Anforderungen aus 01-requirements.md (FR-1.x, NFR-2) und 02-architecture.md (13.1–13.5). JWT-Implementation, Passwort-Hashing und Endpoint-Access-Control sind korrekt implementiert. Keine kritischen Findings.
Einzig offener Punkt: CSP-Header muss vor Produktion auf Nonce umgestellt werden (AUTH-12), da die aktuelle Prod-CSP Alpine.js-Inline-Skripte blockieren würde. Dies ist ein geplanter v1.1-Task.
Empfehlungen für Phase 7 (Quality-Reviewer)
- CSP-Nonce-Migration vor Deployment – Prod-CSP aktuell ohne
unsafe-inline→ Frontend funktioniert nicht. Muss vor Production-Release behoben werden. - Password-Hashing-Verifikation – Sicherstellen, dass
bcrypt==4.0.1korrekt gepinnt ist (4.1+ bricht passlib). - Token-Expiry-Test automatisieren –
test_auth.py:test_expired_token_returns_401prüft explizittoken_expired_or_invalid, aber Integration-Test könnte race-condition beiiat/exphaben. - Rate-Limiting-Akzeptanz prüfen – Ohne LoginAttempt-Tabelle (v1.1) ist der Login-Endpoint ungebremst. In Phase 7 dokumentieren, ob dies für v1-Go-Live akzeptabel ist.