# Sicherheits-Audit — andreknie.de > Stand-Analyse des Backends (Express 5), des Frontends (Vite/React SPA) und der Deployment-Konfiguration (Docker + Caddy). > Bewertung: 🔴 kritisch (vor Go-Live beheben) · 🟡 wichtig (zeitnah) · 🟢 ok / Hinweis --- ## Zusammenfassung Die Architektur ist grundsolide: Double-Opt-In für alle Formulare, keine Datenbank (Dateispeicher), keine Tracking-Cookies, Secrets über `.env` (korrekt aus Git ausgeschlossen), TLS + Security-Header über Caddy. Vor dem Livegang gibt es jedoch mehrere Lücken, vor allem **fehlendes Rate-Limiting**, **keine Absicherung gegen automatisierte Spam-Bots** und ein **Path-Traversal-Risiko beim Resource-Download**. --- ## 🔴 Kritisch — vor Go-Live beheben ### 1. Kein Rate-Limiting — ✅ BEHOBEN Die Planung (Task 8.1) sah `server/middleware/rateLimiter.js` vor (contact 3/h, talk 3/h, newsletter 5/h, resource 10/h). Dieses Verzeichnis existiert nicht. **Alle Endpunkte sind unbegrenzt aufrufbar.** - **Risiko:** Mail-Flooding (jede Anfrage löst eine E-Mail aus → Versand-Kontingent/Reputation gefährdet), Denial-of-Service, Befüllen der JSON-Dateien bis zur Plattenauslastung. - **Fix (umgesetzt):** `express-rate-limit` in `server/middleware/rateLimiter.js` mit Limits pro Endpunkt (contact/talk 3/h, newsletter 5/h, resource 10/h). `app.set('trust proxy', 1)` gesetzt für korrekte Client-IP hinter Caddy. Verifiziert: 4. Kontakt-Request → 429. ### 2. Kein Spam-/Bot-Schutz — ✅ BEHOBEN Formulare haben keinerlei Schutz gegen automatisiertes Absenden (kein Honeypot, kein Captcha, keine Zeitfalle). - **Risiko:** Da jede Übermittlung eine Bestätigungsmail an eine vom Absender frei wählbare Adresse auslöst, lässt sich die Seite als **Spam-Schleuder / Backscatter-Quelle** missbrauchen. - **Fix (umgesetzt):** `server/middleware/antiSpam.js` mit Honeypot-Feld (`company_website`) + serverseitiger Zeitfalle (`form_started_at`, < 2 s = Bot). Erkannte Bots erhalten ein Fake-201 (lernen nicht, dass sie erkannt wurden). Frontend: verstecktes Honeypot-Feld im Kontaktformular, Zeitstempel automatisch in `useFormSubmit`. Verifiziert: gefülltes Honeypot/Zeitfalle → kein Lead/keine Mail. ### 3. Path-Traversal beim Resource-Download — ✅ BEHOBEN In `resource-download.js` wird die Download-URL direkt aus dem Input gebaut: ```js res.json({ ok: true, download_url: `/resources/${resource_id}.pdf` }) ``` - **Risiko:** `resource_id` ist ungeprüft. Werte wie `../../etc/passwd%00` oder `../../assets/secret` könnten je nach Auslieferung manipulierte Pfade erzeugen. - **Fix (umgesetzt):** `resource_id` wird strikt gegen `^[a-z0-9]+(-[a-z0-9]+)*$` validiert; ungültige IDs → 400. Verifiziert: `../../etc/passwd` abgelehnt, kein Lead angelegt. --- ## 🟡 Wichtig — zeitnah ### 4. Fehlende Security-Header am Express-Server (`helmet`) — ✅ BEHOBEN Caddy setzt zwar HSTS, X-Frame-Options etc. für statische Auslieferung. Die `/api/*`-Antworten kommen aber direkt aus Express. - **Fix (umgesetzt):** `helmet()` eingebunden; `app.disable('x-powered-by')` gesetzt. ### 5. Keine Body-Size-Begrenzung — ✅ BEHOBEN - **Fix (umgesetzt):** `express.json({ limit: '10kb' })`. ### 6. Race Condition / Datenverlust beim Datei-Speicher `loadTokens()/saveTokens()`, `subscribers.json`, `leads.json` arbeiten mit synchronem Read-Modify-Write ohne Locking. Bei gleichzeitigen Requests kann ein Schreibvorgang den anderen überschreiben. - **Risiko:** Verlorene Subscriber/Leads, korrupte JSON. - **Fix:** Für geringe Last vertretbar, aber dokumentieren. Mittelfristig serialisieren (Write-Queue) oder auf SQLite umsteigen. ### 7. Content-Security-Policy fehlt Weder Caddy noch helmet setzen aktuell eine CSP. - **Risiko:** Erhöhte XSS-Auswirkung. React escaped zwar per Default, aber Kniepunkt-Inhalte werden über `marked` als HTML gerendert (`body: marked(content)`) und via `dangerouslySetInnerHTML` (zu prüfen) eingebunden. - **Fix:** CSP in Caddy setzen (`default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src fonts.gstatic.com; ...`). Markdown-Quellen sind selbst verfasst (geringes Risiko), aber CSP ist Defense-in-Depth. ### 8. CORS-Origin unflexibel `origin: 'https://andreknie.de'` — `www.`-Variante würde abgelehnt. Prüfen, ob `www.andreknie.de` per Caddy auf die Apex-Domain umgeleitet wird (dann ok). --- ## 🟢 OK / positiv - **Secrets:** `.env` und `server/data/*.json` korrekt in `.gitignore`. Keine Hardcoded-Credentials im Code. - **Double-Opt-In:** Sauber umgesetzt, Tokens via `crypto.randomUUID()`, Ablauf 24h/48h, stündliches Cleanup. - **Token-Verbrauch:** Tokens werden bei Bestätigung als `confirmed` markiert → kein Replay. - **Eingabevalidierung:** Längen- und Formatprüfungen pro Feld vorhanden (Name, E-Mail, Nachricht). - **Keine Tracking-Cookies / keine Drittanbieter-Analytics** (datenschutzfreundlich, deckt sich mit Datenschutzerklärung). - **TLS:** Auto-HTTPS via Caddy/Let's Encrypt. - **E-Mail-Inhalte:** Als `text` (kein HTML) versendet → kein HTML-Injection-Vektor in Mails. - **Fehler-Handler:** Gibt generische Meldung aus, leakt keine Stacktraces an den Client. --- ## Abhängigkeits-Hinweise - `npm audit`: nodemailer v6 hatte eine **high-severity** Vulnerability (GHSA-mm7p-fcc7-pg87 / GHSA-rcmh-qjqh-p98v). **Behoben** durch Upgrade auf `nodemailer@^8`. Aktueller Stand: **0 Vulnerabilities**. - Versionen sind aktuell gepinnt mit `^` (Caret) — für reproduzierbare Builds ist `package-lock.json` vorhanden und maßgeblich. --- ## Maßnahmen-Checkliste (priorisiert) - [x] 🔴 `express-rate-limit` + `trust proxy` einbauen - [x] 🔴 Honeypot + Zeitfalle in allen Formularen (serverseitig für alle Endpunkte aktiv) - [x] 🔴 `resource_id`-Regex-Validierung - [x] 🟡 `helmet` + `x-powered-by` deaktivieren - [x] 🟡 `express.json({ limit: '10kb' })` - [x] 🟢 `npm audit` ausführen → nodemailer@8, 0 Vulnerabilities - [ ] 🟡 CSP in Caddyfile ergänzen - [ ] 🟡 Schreibzugriffe auf JSON serialisieren (oder SQLite-Migration planen) - [ ] 🟢 `www.`→Apex-Redirect in Caddy prüfen