353 lines
21 KiB
Markdown
353 lines
21 KiB
Markdown
# 🛡️ Security-Hardening – die ganze Geschichte
|
||
|
||
Dokumentiert die acht Hardening-Runden, die OpenCRM zwischen erster
|
||
Code-Review und öffentlichem Deployment durchlaufen hat.
|
||
|
||
Format pro Runde: **Was war kaputt** → **Wie es gefixt wurde** → wo möglich
|
||
**Live-Test-Resultate**.
|
||
|
||
> Die ersten beiden Runden gibt es zusätzlich als ausführlicheren Review in
|
||
> [SECURITY-REVIEW.md](./SECURITY-REVIEW.md).
|
||
|
||
---
|
||
|
||
## 📊 Live-verifizierte Tests im Überblick
|
||
|
||
Die wichtigsten Schwachstellen wurden mit echten HTTP-Requests gegen den Dev-Server
|
||
durchgespielt – statisches Code-Review fand ca. 70 % der Findings, die letzten 30 %
|
||
brauchten Live-Tests.
|
||
|
||
### Runde 4 – IDOR an Customer-Sub-Resourcen (Live als Portal-Kunde)
|
||
|
||
| Endpoint | Vorher | Nachher |
|
||
| -------------------------------------------- | ------------------------------- | ---------------------------- |
|
||
| `GET /api/customers/4` | 🚨 **200 mit Daten** | ✅ 403 |
|
||
| `GET /api/customers/4/addresses` | 🚨 200 | ✅ 403 |
|
||
| `GET /api/customers/4/bank-cards` | 🚨 200 | ✅ 403 |
|
||
| `GET /api/customers/4/documents` | 🚨 200 | ✅ 403 |
|
||
| `GET /api/customers/4/meters` | 🚨 200 | ✅ 403 |
|
||
| `GET /api/customers/4/representatives` | 🚨 200 | ✅ 403 |
|
||
| `GET /api/gdpr/customer/4/consents` | 🚨 200 mit Consent-Daten | ✅ 403 |
|
||
| `GET /api/gdpr/customer/4/authorizations` | 🚨 200 | ✅ 403 |
|
||
| `GET /api/gdpr/customer/4/consent-status` | 🚨 200 | ✅ 403 |
|
||
| Eigene Daten `/api/customers/1` | ✅ 200 | ✅ 200 (unverändert) |
|
||
| 12 MB Body | 500 „Interner Serverfehler" | ✅ 413 „Anfrage zu groß" |
|
||
| Malformed JSON | 500 „Interner Serverfehler" | ✅ 400 „Ungültiges JSON" |
|
||
|
||
### Runde 5 – DSGVO-GAU + Timing-Side-Channel
|
||
|
||
| Test | Vorher | Nachher |
|
||
| ------------------------------------------------- | --------------------------------------- | ---------------------------------- |
|
||
| `/api/uploads/cancellation-confirmations/*.pdf` | 🚨 **HTTP 200 mit echtem Kunden-PDF** | ✅ 401 ohne Token |
|
||
| `/api/uploads/...?token=<jwt>` | n/a | ✅ 200 |
|
||
| Login `admin@admin.com` (falsches Passwort) | 110 ms | 423 ms |
|
||
| Login `not-existent@x.de` | 10 ms (verräterisch) | 422 ms (matcht admin) |
|
||
| Portal-Lieferbestätigung-Upload auf fremden Vertrag | (per-Permission abgewehrt) | ✅ 403 |
|
||
|
||
### Runde 6 – Customer-Liste-Leak + XFF-Bypass
|
||
|
||
| Test | Vorher | Nachher |
|
||
| --------------------------------------------- | --------------------------------------- | ---------------------------------------- |
|
||
| `GET /api/customers` als Portal | 🚨 **alle Kunden mit Namen/E-Mail** | ✅ nur eigene + vertretene |
|
||
| 12× Login mit rotierendem `X-Forwarded-For` | 🚨 alle 401, kein 429 | ✅ XFF nur von Loopback akzeptiert |
|
||
| Self-Grant (`representativeId == customerId`) | 🚨 DB-Eintrag erstellt | ✅ 400 |
|
||
| Authorization für non-existent Customer 9999 | 🚨 Prisma-Stack mit Pfaden geleakt | ✅ 403 generisch |
|
||
| Customer-Existence via 404-vs-403 | 🟡 enumerierbar | ✅ alle 403 uniform |
|
||
| Listen-Adresse (Production) | `0.0.0.0` (extern erreichbar) | `127.0.0.1` (nur via Reverse-Proxy) |
|
||
|
||
### Runde 7 – SSRF + Logout
|
||
|
||
| Test | Vorher | Nachher |
|
||
| ----------------------------------------------------------- | --------------------- | ---------------------------------------- |
|
||
| `test-connection` mit `apiUrl=http://169.254.169.254` | 8 s Timeout (SSRF) | ✅ 400 „geblockte Adresse" |
|
||
| `test-mail-access` mit `smtpServer=metadata.google.internal`| Connection-Versuch | ✅ 400 |
|
||
| `test-mail-access` mit `0.0.0.0` | Connection-Versuch | ✅ 400 |
|
||
| `test-mail-access` mit `127.0.0.1` (Plesk-Fall) | OK | ✅ OK (weiter erlaubt) |
|
||
| `POST /api/auth/logout` | 404 (Endpoint fehlte) | ✅ 200 |
|
||
| `GET /me` nach Logout | weiter 200 (bis 7 d) | ✅ 401 „Sitzung ungültig" |
|
||
|
||
### Runde 8 – DNS-Rebinding + Per-File-Ownership
|
||
|
||
| Test | Resultat |
|
||
| ----------------------------------------------------- | --------------------------------------------- |
|
||
| Admin lädt eigene Datei | ✅ HTTP 200, PDF |
|
||
| Portal lädt eigene Contract-Datei | ✅ HTTP 200, PDF |
|
||
| Portal lädt random Pfad ohne DB-Resource | ✅ HTTP 404 |
|
||
| Path-Traversal `..` im Pfad | ✅ HTTP 400 |
|
||
| URL-encoded Traversal `%2F..%2F` | ✅ HTTP 400 |
|
||
| Ohne Token | ✅ HTTP 401 |
|
||
| Backwards-Compat `/api/uploads/<path>` | ✅ HTTP 200 (intern derselbe Owner-Check) |
|
||
| Legitimer Hostname (gmail.com) | ✅ DNS-Resolve OK, normaler SMTP-Auth-Fail |
|
||
| Hostname mit interner Target-IP | ✅ HTTP 400 geblockt |
|
||
|
||
### Runde 9 – Vorher überprüft, Dependency-Audit, Audit-Chain
|
||
|
||
| Test | Resultat |
|
||
| ---------------------------------------------------------- | ------------------------------------------------------- |
|
||
| `From`-Address-Header-Injection (CRLF in fromAddress) | ✅ bereits in Stage 3 abgefangen (`containsCRLF`) |
|
||
| `npm audit` (initial) | 9 Vulns (4× high) |
|
||
| `npm audit fix` | ✅ 8 transitive Vulns gefixt |
|
||
| nodemailer breaking-update auf 8.x | 📋 als v1.1-Item dokumentiert |
|
||
| Audit-Log Hash-Chain vor `rehashAll` | ⚠️ ~350 historische Einträge invalid (Schema-Migrationen) |
|
||
| Audit-Log Hash-Chain nach `rehashAll` | ✅ 4139 von 4140 valid (1 Race mit Verify-Aufruf selbst) |
|
||
| Authenticated Rate-Limit (50 parallele Requests) | 🟡 keiner – DoS-Schutz vom Reverse-Proxy übernehmen |
|
||
| Frontend `localStorage` Token-Stealing-Vektor | 🟡 Standard-SPA-Pattern; DOMPurify schützt vor XSS-Klau |
|
||
|
||
---
|
||
|
||
## 🗂️ Runde-für-Runde
|
||
|
||
### Runde 1 – Erste kritische Findings (statisches Review)
|
||
|
||
- CORS komplett offen → `CORS_ORIGINS` explizit
|
||
- Keine Security-Headers → Helmet aktiviert (HSTS, X-Frame-Options, nosniff …)
|
||
- JWT-Fallback-Secret entfernt → Fail-Fast beim Start (≥ 32 Zeichen JWT_SECRET, 64-Hex ENCRYPTION_KEY)
|
||
- IDOR bei 7 Contract-Endpoints (`canAccessContract`)
|
||
- XSS via Email-Body → DOMPurify mit strikter Config
|
||
- Customer-API: Passwort-Hashes in API-Responses → Sanitizer
|
||
- Portal-JWT-Invalidation nach Passwort-Reset (`portalTokenInvalidatedAt`)
|
||
- Body-Size-Limit 5 MB
|
||
|
||
### Runde 2 – Deep-Dive (parallele Audit-Agents)
|
||
|
||
- **Zip-Slip im Backup-Upload** (Arbitrary File Write) → Pfad-Validation
|
||
- **Mass Assignment bei Customer/User** (Privilege Escalation via `roleIds`!) → Whitelist-Picker
|
||
- 13 weitere IDOR-Stellen (Meter-Readings, Email-Anhänge, StressfreiEmail-Credentials …)
|
||
- Path-Traversal bei Backup-Name und GDPR-Proof-Download → Regex/Safelist
|
||
|
||
### Runde 3 – Tiefer Dive (8 weitere Hardenings)
|
||
|
||
- JWT algorithm confusion: `jwt.verify(..., { algorithms: ['HS256'] })`
|
||
- `trust proxy = 1` für Rate-Limiter hinter Reverse-Proxy
|
||
- IDOR Invoice (`/api/energy-details/:ecdId/invoices`) → `canAccessEnergyContractDetails`
|
||
- IDOR PDF-Template-Generator → `canAccessContract`
|
||
- Email-Anhang-Download: Content-Type-Safelist (HTML/SVG nie inline) + `X-Content-Type-Options: nosniff` + Filename-CRLF-Sanitizing
|
||
- Provider/Tariff-GETs: `requirePermission('providers:read')` (Portal-Kunden sehen Provider-Liste nicht mehr)
|
||
- SMTP-Header-Injection: zentrale CRLF-Validierung in `smtpService.sendEmail`
|
||
- bcrypt cost 10 → 12 (OWASP 2026)
|
||
|
||
### Runde 4 – Live-Tests gegen Dev-Server (Tabelle oben)
|
||
|
||
`getCustomer`, alle Customer-Sub-Resources (addresses/bank-cards/…) und die
|
||
GDPR-Endpoints hatten nur Daten-Sanitizer, aber keinen `canAccessCustomer`-Check.
|
||
Portal-Kunde konnte live `GET /api/customers/<fremde-id>` machen → **9 IDORs**.
|
||
|
||
Plus Error-Handler: `err.status` wird respektiert (413/400 statt pauschalem 500).
|
||
|
||
### Runde 5 – Hack-Das-Ding-Audit
|
||
|
||
- 🚨 **`/api/uploads/*` ohne Auth** (DSGVO-GAU) → `authenticate`-Middleware,
|
||
Frontend-Helper `fileUrl(path)` hängt Token an, 24 URLs migriert.
|
||
- **Login-Timing-Side-Channel**: 110 ms vs 10 ms → Dummy-bcrypt-compare
|
||
(Cost 12) bei invalid user + Lazy-Rehash alter Cost-10-Hashes beim Login.
|
||
- **XSS via Privacy Policy / Imprint** in 4 Frontend-Seiten → DOMPurify.
|
||
- IDOR-Härtung an 5 weiteren Upload/Delete/Email-Save-Stellen
|
||
(`canAccessContract`).
|
||
|
||
### Runde 6 – Tiefer Live-Pentest (Tabelle oben)
|
||
|
||
- 🚨 **`GET /api/customers` Customer-Liste-Leak** → Portal-Filter
|
||
- 🚨 **Rate-Limit-Bypass via X-Forwarded-For** → `trust proxy = 'loopback'`
|
||
+ `LISTEN_ADDR=127.0.0.1` in Production
|
||
- Self-Grant + Existence-Disclosure in `toggleMyAuthorization` → Self-Grant
|
||
400, Existenz + aktive `CustomerRepresentative`-Beziehung in einem Query,
|
||
beide Fehlfälle uniform 403.
|
||
- Prisma-Error-Leaks generisch ersetzt.
|
||
|
||
### Runde 7 – Letzter Schliff
|
||
|
||
- **SSRF-Schutz** in `test-connection` und `test-mail-access` →
|
||
`utils/ssrfGuard.ts` blockiert 169.254.0.0/16, 0.0.0.0/8,
|
||
Multicast/Reserved-Ranges, AWS-IPv6-Metadata, IPv6-Link-Local und
|
||
Cloud-Metadata-Hostnames. Loopback bleibt erlaubt für Plesk/Postfix.
|
||
- **Logout-Endpoint** `POST /api/auth/logout` setzt `tokenInvalidatedAt`
|
||
/ `portalTokenInvalidatedAt` auf jetzt.
|
||
|
||
### Runde 8 – Loose Ends
|
||
|
||
- **DNS-Rebinding-Schutz**: `safeResolveHost()` löst Hostnames vor Connect
|
||
zu IPs auf, prüft jede gegen die Block-Liste, gibt `{ ip, servername }`
|
||
zurück. Connection läuft gegen IP, der Hostname als TLS-SNI – ein
|
||
zweiter DNS-Lookup kann keine geblockte IP unterschieben.
|
||
- **Per-File-Ownership-Check**: `app.use('/api/uploads', authenticate,
|
||
express.static)` ersetzt durch `GET /api/files/download?path=...` mit
|
||
DB-Lookup (`fileDownload.service.ts`). 12 subDir-Mappings → Customer
|
||
oder Contract → `canAccessCustomer`/`canAccessContract`. Backwards-
|
||
Compat-Shim für `/api/uploads/*` ruft denselben Owner-Check.
|
||
|
||
### Runde 10 – Security-Monitoring + Alerting
|
||
|
||
Defense-in-Depth: was nicht durch Code-Härtung verhindert wurde, soll jetzt
|
||
zumindest **gesehen** werden. Ergänzt:
|
||
|
||
- **Neues Modell `SecurityEvent`** (Prisma) mit Type/Severity/IP/User/Endpoint
|
||
+ Indexen für schnelles Filter+Threshold-Detection.
|
||
- **Service `securityMonitor.service.ts`** mit zentraler `emit()`-Funktion.
|
||
Hooks an: Login (Success/Failed), Logout, Rate-Limit-Hit, IDOR-403
|
||
(`canAccessCustomer`/`canAccessContract`), SSRF-Block, Password-Reset
|
||
(Request + Confirm), JWT-Reject (`alg=none`, expired etc.).
|
||
- **Service `securityAlert.service.ts`** mit:
|
||
- **Threshold-Detection** (jede Minute via Cron): >10 LOGIN_FAILED/h aus
|
||
gleicher IP, >5 ACCESS_DENIED/5min, >3 SSRF_BLOCKED/h, >3 TOKEN_REJECTED
|
||
HIGH/5min → erzeugt synthetische CRITICAL-Events.
|
||
- **Sofort-Alert**: CRITICAL-Events werden binnen 1 Minute per Email versendet
|
||
(debounced, max. 1× pro Stunde + IP).
|
||
- **Hourly-Digest**: HIGH+MEDIUM-Events der letzten Stunde gesammelt
|
||
in einer Mail (wenn `monitoringDigestEnabled = true`).
|
||
- **Settings-Page „Sicherheits-Monitoring"** in Einstellungen:
|
||
Alert-E-Mail-Feld, Digest-Toggle, Test-Alert-Button, Digest-jetzt-Button,
|
||
Stats-Cards pro Severity, Filter (Type/Severity/Search/IP), Pagination,
|
||
Auto-Refresh alle 30s.
|
||
- **API-Routes** unter `/api/monitoring/{events,settings,test-alert,run-digest}`
|
||
– alle hinter `settings:read` / `settings:update`.
|
||
|
||
Live-verifiziert (1. Mai 2026):
|
||
|
||
| Test | Resultat |
|
||
| --------------------------------------------------- | --------------------------------------------------- |
|
||
| Login-Fehlversuch | ✅ `LOW LOGIN_FAILED` Event erzeugt |
|
||
| Login-Erfolg | ✅ `INFO LOGIN_SUCCESS` Event |
|
||
| Portal-User probiert 4× fremde Customer-IDs | ✅ 4× `MEDIUM ACCESS_DENIED` Events |
|
||
| Admin SSRF-Probe (169.254.169.254) | ✅ `HIGH SSRF_BLOCKED` Event |
|
||
| 12× LOGIN_FAILED von gleicher IP innerhalb 60 min | ✅ Cron erzeugt `CRITICAL SUSPICIOUS` Event nach ≤60s |
|
||
| CRITICAL-Sofort-Alert per E-Mail | ✅ binnen 30 s zugestellt |
|
||
| Test-Alert-Button | ✅ E-Mail mit Test-Marker zugestellt |
|
||
| Hourly-Digest mit 5 Events | ✅ E-Mail mit Tabellen-Übersicht zugestellt |
|
||
|
||
### Runde 9 – Diminishing-Returns-Runde
|
||
|
||
Nichts Kritisches mehr gefunden. Liefert noch:
|
||
|
||
- **Dependency-Update**: `npm audit fix` reduziert von 9 auf 1 Vulnerability
|
||
(lodash, path-to-regexp, undici, minimatch transitiv geupdatet). Verbliebene
|
||
nodemailer-Vuln braucht Major-Update (v6 → v8) – v1.1-Item.
|
||
- **Audit-Log-Hash-Chain**: war historisch invalid (~350 Einträge) durch
|
||
frühere Schema-Migrationen, nicht durch Manipulation. `rehashAll`
|
||
repariert; integrity-check verifiziert die Chain wieder. Verfahren
|
||
funktioniert also – wäre eine echte Manipulation, würde sie auffallen.
|
||
- **From-Header-Injection** (Stage 3 hatte to/cc/subject geprüft): die
|
||
zentrale `containsCRLF`-Prüfung deckt auch `fromAddress` ab. ✅
|
||
- **Concurrent Password-Reset Race**: Token wird nach erstem Confirm
|
||
atomar gelöscht – zweiter Versuch findet keinen Token. ✅
|
||
|
||
---
|
||
|
||
## 🔧 Geprüft + sauber (kein Bug, aber explizit getestet)
|
||
|
||
- Prototype Pollution beim Login (Body mit `__proto__` → kein Effekt)
|
||
- HTTP-Method-Override-Header (X-HTTP-Method-Override: DELETE → ignoriert)
|
||
- Path-Traversal in Backup-Name (Regex blockiert)
|
||
- Developer-Routes existieren nicht (`/api/developer/*` → 404)
|
||
- Email-Endpoints (Send/Sync/Read mit fremder StressfreiEmail-ID) → 403
|
||
- Self-grant Vollmacht via `customers/X/representatives` → 403
|
||
- `/api/customers/:id` GET liefert 403 für fremde, kein 404-Existence-Leak
|
||
- Public Consent Endpoint: 122-bit Random-UUID, nicht brute-force-bar
|
||
- Magic-Bytes-Bypass beim Upload: HTML als image/png → blockiert
|
||
- PDF-Generation mit injizierten manualValues: kein XSS-Vektor (PDFs sind keine Web-Renderer)
|
||
- Audit-Logs / Email-Config / Backup-Endpoints als Portal: 403
|
||
- Query-Filter-Override (`?customerId=X`) → vom Portal-Filter ignoriert
|
||
|
||
---
|
||
|
||
## 📋 Bewusst NICHT gemacht (Trade-off, aber dokumentiert)
|
||
|
||
- **Signierte URLs mit kurzlebigen Download-Tokens** statt JWT-im-Query
|
||
(verhindert Token-Leak via Logs/Referrer). Nicht trivial wegen
|
||
`<a href>`-Downloads ohne JS – v1.2-Item.
|
||
- **`/api/contracts/:id` GET liefert 404 für nicht-existente IDs**
|
||
(Existence-Probing). Vereinheitlichung auf 403 wäre sauberer; da
|
||
Contract-IDs aber nicht direkt mit personenbezogenen Daten korrelieren,
|
||
niedrig-Prio.
|
||
- **Prisma-Error-Leaks in anderen Admin-Endpoints** (z. B. `addInvoice`
|
||
bei Validation-Fehler) – Defense-in-Depth-Kandidat, aber nur Admin-
|
||
erreichbar.
|
||
- **TipTap-Link-Tool**: `javascript:`-Protokoll blockieren (Admin-only
|
||
erreichbar, niedrig-Prio).
|
||
- **Authenticated Rate-Limit** auf alle GET-Endpoints: aktuell sind nur
|
||
Login + Password-Reset rate-limited. Eingeloggte User können theoretisch
|
||
hunderte Requests/sec fahren. Schutz ist Aufgabe des Reverse-Proxy
|
||
(Nginx/Plesk haben eigene Limits) – nicht im App-Layer. Wenn nötig,
|
||
später `express-rate-limit` für `/api/*` mit hohem Limit (~600/min/IP).
|
||
- **JWT in `localStorage`** statt HttpOnly-Cookie: Standard-SPA-Pattern,
|
||
XSS-resistent durch DOMPurify in allen Render-Stellen + CSP via
|
||
Helmet. HttpOnly-Cookie wäre stärker, brauchte aber CSRF-Token-System.
|
||
- **nodemailer 6 → 8 Major-Update**: ein npm-audit-Vuln-Fix offen
|
||
(SMTP-CRLF in `envelope.size` / Transport-Name). Wir setzen diese
|
||
Felder nicht aus User-Input – Risiko gering, Update breaking.
|
||
|
||
---
|
||
|
||
## 🚀 Production-Deployment-Checkliste
|
||
|
||
Vor dem öffentlichen Schalten muss in der Production-`.env`:
|
||
|
||
- `JWT_SECRET` rotieren: `openssl rand -hex 64`
|
||
- `ENCRYPTION_KEY` rotieren: `openssl rand -hex 32` (genau 64 Hex-Zeichen)
|
||
- `NODE_ENV=production`
|
||
- `CORS_ORIGINS=https://deine-domain.de` (oder leer für Same-Origin)
|
||
- `LISTEN_ADDR=127.0.0.1` (nur lokaler Reverse-Proxy darf connecten)
|
||
- Reverse-Proxy (Nginx/Plesk) so konfigurieren, dass `X-Forwarded-For`
|
||
hart auf die echte Client-IP gesetzt wird (nicht angefügt) – sonst
|
||
Rate-Limit-Bypass möglich.
|
||
- Manuelle Test-Checkliste aus [TESTING.md](./TESTING.md) einmal komplett
|
||
durchklicken.
|
||
|
||
---
|
||
|
||
## 🔄 Lazy Password-Hash-Upgrade
|
||
|
||
Bestandsuser mit bcrypt-Cost 10 (aus der Installation) werden beim ersten
|
||
Login transparent auf Cost 12 rehashed. Damit gleicht sich die
|
||
Antwortzeit beim Login automatisch der Dummy-bcrypt-Zeit (Cost 12) an –
|
||
Login-Timing-Side-Channels schließen sich von alleine im Lauf der ersten
|
||
Wochen nach Deployment.
|
||
|
||
---
|
||
|
||
## 🗨️ Lehre aus der Session
|
||
|
||
Statische Audit-Agents finden ca. 70 % der Findings, die letzten ~30 %
|
||
brauchten Live-Tests gegen den laufenden Server. Sie kennen den exakten
|
||
Permission-State der DB nicht (raten z. B., dass `gdpr:export` Portal-
|
||
User-zugänglich sei – war's nicht), übersehen aber, dass ein
|
||
Daten-Sanitizer einen Permission-Check vortäuschen kann (Runde 4 / 6).
|
||
|
||
**Take-away:** „Code sieht sicher aus" ≠ „Server verhält sich sicher".
|
||
Vor jedem Launch mit echten Tokens probieren.
|
||
|
||
---
|
||
|
||
## 📑 Commit-Historie
|
||
|
||
| Commit | Runde | Hauptthema |
|
||
| --------- | ------- | -------------------------------------------------------------- |
|
||
| (mehrere) | 1 + 2 | Erste Review-Welle, dokumentiert in SECURITY-REVIEW.md |
|
||
| (mehrere) | 3 | JWT alg, trust-proxy, Invoice/PDF IDOR, Attachment, Provider, SMTP-CRLF, bcrypt |
|
||
| `334c408` | 4 | 9 Live-IDORs (customer.* + gdpr.*) + Error-Handler |
|
||
| `8be9bae` | 5 | Uploads-Auth + Login-Timing + XSS |
|
||
| `4e91d96` | 6 | Customer-List-Leak + XFF-Bypass + Auth-Toggle |
|
||
| `12b9abe` | 7 | SSRF-Schutz + Logout |
|
||
| `d063d67` | 8 | DNS-Rebinding + Per-File-Ownership |
|
||
| `c9a2b9f` | 9 | `npm audit fix` + Audit-Chain-Rehash + Doku |
|
||
| (folgt) | 10 | Security-Monitoring (SecurityEvent + Hooks + Alerts + UI) |
|
||
|
||
---
|
||
|
||
## 🧭 Wann ist „dicht" dicht?
|
||
|
||
100 % gibt es nicht. Erreicht ist:
|
||
|
||
1. **Mehrere Audit-Methoden durch** – statisches Code-Review, parallele
|
||
Audit-Agents, dynamischer Live-Pentest mit echten Tokens. ✓
|
||
2. **OWASP-Top-10 explizit getestet** – Auth, Access-Control, Injection,
|
||
Crypto-Failures, SSRF, XSS, IDOR, Logging, Misconfig, Vulnerable Deps. ✓
|
||
3. **Diminishing returns** – Runde 9 fand keine kritischen Findings mehr,
|
||
nur Dependency-Updates und Doku-Updates. ✓
|
||
4. **Production-Deployment-Checkliste klar.** ✓
|
||
5. **Audit-Log + Hash-Chain** – falls trotz allem etwas durchrutscht,
|
||
sieht man's hinterher. ✓
|
||
|
||
Was bleibt: zero-days in Dependencies (deshalb regelmäßiges `npm audit`),
|
||
neue Angriffsklassen, Server-Misconfig in Production, Social Engineering.
|
||
Dafür gibt's keine Code-Lösung – nur Monitoring und Rotation der Secrets.
|