MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 8s
11 KiB
11 KiB
Bekannte Schwachstellen, Risiken & Technische Schulden (Flaws)
Diese Datei dokumentiert eine ehrliche und schonungslose technische Analyse aller bekannten Limitierungen, Architektur-Kompromisse, Skalierungsrisiken und UX-Schwachstellen in MindSift. Sie dient als verbindlicher Backlog für zukünftige Refactorings und Architektur-Meilensteine.
📌 Übersicht & Risikomatrix
| ID | Bereich | Problem / Schwachstelle | Schweregrad | Risiko im Produktivbetrieb |
|---|---|---|---|---|
| FLAW-01 | Workspace Sync | Blockierende Locks / Synchronisations-Konflikte | Gelöst (v2.2) | Behoben durch In-Memory DocRooms, Echo-Schutz per clientId und 1.5s Debounce-Flush |
| FLAW-02 | Storage | Dual-Storage Drift (SQLite vs. Dateisystem) | Obsolet / Gelöst | Kein Risiko: MindSift nutzt Pure-DB (keine .md-Dateien auf Platte) |
| FLAW-03 | Netzwerk / WS | Proxy-Inkompatibilität bei WebSocket-Upgrades & Polling-Fallback | Gelöst (v2.2) | Behoben durch Dual-Transport SSE-Fallback & globale Singleton-Verbindung |
| FLAW-04 | Datenbank / Suche | Volltextsuche via LIKE '%query%' statt SQLite FTS5 |
Mittel | Performance-Einbrüche bei wachsenden Notizbeständen (>10.000 Blöcke) |
| FLAW-05 | Frontend DOM | Fehlende Virtualisierung im Outliner-Baum | Mittel | Frame-Drops und Lags bei Dokumenten mit >500 Blöcken auf Mobilgeräten |
| FLAW-06 | SQLite Concurrency | Single-Writer-Bottleneck bei hohem parallelem Schreibaufkommen | Mittel | SQLITE_BUSY Fehler bei vielen gleichzeitigen Team-Nutzern |
| FLAW-07 | Benachrichtigungen | Fehlende Dead-Letter-Queue für Webhook- & E-Mail-Alarme | Niedrig | Verlorene Benachrichtigungen bei temporärem Ausfall von Drittsystemen |
| FLAW-08 | Sicherheit / Auth | Statische JWT-Tokens ohne rotierende Refresh-Tokens & Blacklisting | Niedrig | Keine sofortige Sitzungsinvalidierung bei Rechteentzug |
🔍 Detaillierte Analyse & Maßnahmen
FLAW-01: [GELÖST / IMPLEMENTIERT] Granulare Outliner-Synchronisation & Multi-User Kollaboration
- Status: Gelöst via In-Memory DocRooms, Echo-Schutz & Debounced Persistence
- Betroffene Komponenten:
api/websocket.md,modules/workspace.md,internal/ws/docroom.go,internal/storage/sqlite.go,frontend/src/lib/useDocumentSync.ts,frontend/src/features/workspace/OutlinerBlock.tsx - Ursache (Historisch): MindSift nutzte früher pessimistisches Dokumentensperren (Lease-Locks mit Heartbeats). Bei parallelem Bearbeiten blockierten sich Nutzer gegenseitig; unausgereifte Sync-Versuche führten zu Selbstechos und Cursor-Jumps.
- Implementierte Lösung:
- In-Memory DocRooms: Granularer WebSocket-Raum pro Dokument (
/api/v1/ws/docs/:id) mit Mutex-geschütztem Block-State im Go-Backend. - Strikter Echo-Schutz per
clientId: Jede Verbindung besitzt eine eindeutigeclientId. Der Hub filtert den Sender heraus und leitet Events nur an Peers weiter. - Live Presence & Ephemere Cursors: Cursorposition und Textselektionen (
cursor_moved) werden flüchtig an Peers gestreamt, ohne SQLite zu belasten. - Fokus- & Eingabeschutz (
OutlinerBlock.tsx): Remote-Updates werden nur blockiert, wenndocument.hasFocus()vorliegt. Beiwindow.onblurundinput.onblurwird sofort (0ms) geflusht; Callbacks werden überuseRefgeführt (keine Stale Closures). - 1.5s Inaktivitäts-Debounce-Flush: Nach 1,5s Inaktivität sichert der Raum persistent in SQLite (
blocks, Regex#tagsinblock_tagsund kompiliertes Markdown indocuments.markdown_cache). - Vollständige Eliminierung von Locks: Alle blockierenden Sperren wurden restlos entfernt. Mehrere Nutzer können zeitgleich schreiben.
- In-Memory DocRooms: Granularer WebSocket-Raum pro Dokument (
FLAW-02: [OBSOLET / GELÖST] Dual-Storage Drift (SQLite vs. Dateisystem)
- Status: Entkräftet & Behoben (Pure Database Architecture)
- Klarstellung: In früheren Entwürfen wurde angedacht, Notizen zusätzlich als Markdown-Dateien auf dem Dateisystem zu spiegeln. MindSift speichert Notizen und Blöcke jedoch zu 100% relational in SQLite (
documentsunddocument_blocks). - Verifikation:
- Es existieren keine Markdown-Dateien auf der Festplatte.
- Das Go-Backend schreibt keine Dateien in ein Notizverzeichnis.
- Unnötige Verzeichniserstellungen (
docDir/workspace) wurden ausinternal/storage/sqlite.govollständig entfernt.
- Ergebnis: Es gibt keinen Dual-Storage und somit keinen File-Drift oder externe Dateikonflikte. Notizen verhalten sich vollständig transaktional und konsistent innerhalb der SQLite-Datenbank.
FLAW-03: [GELÖST / IMPLEMENTIERT] Proxy-Inkompatibilität bei WebSocket-Upgrades & Polling-Fallback
- Status: Gelöst & Vollständig Implementiert (Dual-Transport SSE Fallback & Proxy-Resilienz)
- Betroffene Komponenten:
api/websocket.md,reverse-proxy.md,internal/ws/hub.go,internal/api/server.go,frontend/src/lib/api.ts - Problemursache: Corporate Proxies (z.B. Zscaler, BlueCoat, strikte Firewalls) oder unvollständig konfigurierte Reverse-Proxies verwerfen den HTTP-Upgrade-Header
Upgrade: websocket(NS_ERROR_CONNECTION_REFUSED, Status 1006 / 1002). Früher fiel der Client dabei auf 2-Sekunden-Polling zurück, was zu Verzögerungen und Last führte. - Implementierte Lösung:
- Unidirektionaler Server-Sent Events (SSE) Fallback:
- Neuer Streaming-Endpunkt
GET /api/v1/eventsim Go-Backend mitContent-Type: text/event-stream,X-Accel-Buffering: nound 15s Heartbeat-Pings (: ping\n\n). - Standard HTTPS Ports (443/80), wodurch kein Proxy den Datenstrom blockieren kann.
- Neuer Streaming-Endpunkt
- Hub-Multiplexing:
ws.Hubverwaltet WebSocket- und SSE-Clients parallel in gemeinsamen Räumen (rooms,userRooms,allClients).- Sämtliche Events (
BroadcastToAll,BroadcastToUser,BroadcastRoom) werden transparent an beide Transportarten verteilt.
- REST-Parität für Outbound CRDT & Awareness:
PUT /api/v1/documents/:id/crdt: Akzeptiert Delta-Updates (update) und broadcastet sie an alle Peers.POST /api/v1/documents/:id/awarenessundDELETE /api/v1/documents/:id/awareness: Übertragen Blockfokus und Cursor ohne WebSocket.POST /api/v1/events/rooms: Erlaubt SSE-Clients dynamisches Room-Joining.
- Frontend Realtime Transport Manager (
frontend/src/lib/api.ts):- Automatischer 3,5s-Watchdog: Schlägt das WebSocket-Upgrade fehl oder wird Code 1006/1002 gemeldet, wechselt MindSift nahtlos auf
EventSource('/api/v1/events'). - Grüner/türkiser Statusindikator im Header zeigt
Live (WebSocket)bzw.Live (Server-Sent Events / Proxy-Stream).
- Automatischer 3,5s-Watchdog: Schlägt das WebSocket-Upgrade fehl oder wird Code 1006/1002 gemeldet, wechselt MindSift nahtlos auf
- Reverse-Proxy-Handbuch:
- Vollständige Produktions-Konfigurationen für Nginx, Traefik, Caddy, Cloudflare und Apache unter
reverse-proxy.md.
- Vollständige Produktions-Konfigurationen für Nginx, Traefik, Caddy, Cloudflare und Apache unter
- Unidirektionaler Server-Sent Events (SSE) Fallback:
FLAW-04: Volltextsuche via LIKE '%query%'
- Betroffene Komponenten:
database.md,internal/api/search.go,internal/api/thoughts.go - Ursache: Suchabfragen über Gedanken, Notizblöcke und Aufgaben nutzen aktuell einfaches SQL:
WHERE content LIKE '%' || ? || '%' - Folgen:
LIKE '%...'kann B-Tree-Indizes prinzipbedingt nicht nutzen und erzwingt einen sequentiellen Full Table Scan. Bei Datenbanken mit Zehntausenden Blöcken führt dies zu CPU-Spitzen und trägen Suchreaktionen. - Empfohlene Lösung:
- Einrichten einer virtuellen SQLite FTS5-Tabelle (
document_blocks_fts) mit Tokenizer (unicode61odertrigram). - Automatische Synchronisierung über SQLite Trigger (
AFTER INSERT,AFTER UPDATE,AFTER DELETE).
- Einrichten einer virtuellen SQLite FTS5-Tabelle (
FLAW-05: Fehlende Virtualisierung im Outliner-Baum
- Betroffene Komponenten:
modules/workspace.md,frontend/src/features/workspace/WorkspaceView.tsx - Ursache: Jeder Block im Notizbaum wird als eigenes DOM-Element gerendert. Enthält ein Dokument 500 bis 2000 Blöcke (z. B. lange Projektjournale oder Besprechungsprotokolle), befinden sich tausende verschachtelte
div- undcontenteditable-Elemente gleichzeitig im DOM. - Folgen: Auf älteren Smartphones oder bei schnellem Tippen kommt es zu spürbaren Input-Latenzen und Ruckeln beim Scrollen.
- Empfohlene Lösung:
- Implementierung einer virtuellen Fensterung (Virtual Windowing) für Blöcke außerhalb des Sichtbereichs.
- Automatisches Einklappen (Collapsing) tiefer Hierarchieebenen beim initialen Laden langer Dokumente.
FLAW-06: SQLite Concurrency & Single-Writer-Bottleneck
- Betroffene Komponenten:
architecture.md,database.md,internal/storage/sqlite.go - Ursache: Obwohl SQLite mit
PRAGMA journal_mode = WAL;parallele Leser erlaubt, kann zu jedem Zeitpunkt immer nur ein einziger Thread schreiben. Bei intensiver gleichzeitiger Nutzung (z. B. 20 Teammitglieder tippen gleichzeitig in Listen und Notizen, während im Hintergrund Webhook-Events einlaufen) können Schreib-Transaktionen aufeinander warten. - Folgen: Risiko von
database is locked (SQLITE_BUSY)-Fehlern, wenn der Busy-Timeout überschritten wird. - Empfohlene Lösung:
- Setzen von
PRAGMA busy_timeout = 5000;(5 Sekunden Wartezeit vor Fehlerwurf). - Kapselung aller Schreibzugriffe über einen dedizierten Go-Channel / Single-Worker-Pool oder Migration auf PostgreSQL bei größeren Installationen.
- Setzen von
FLAW-07: Fehlende Dead-Letter-Queue für Benachrichtigungen
- Betroffene Komponenten:
modules/tasks.md,internal/scheduler/alarm_ticker.go - Ursache: Der Alarm-Ticker überprüft minütlich fällige Aufgaben und sendet Webhooks bzw. E-Mails. Schlägt der HTTP-Post an den externen Webhook fehl (z. B. Zielserver vorübergehend offline), wird der Fehler lediglich ins Log geschrieben.
- Folgen: Der Alarm wird nicht erneut versucht und die Benachrichtigung geht dauerhaft verloren.
- Empfohlene Lösung:
- Einführung einer Tabelle
notification_outboxmit Spaltenretry_count,next_retry_at,status. - Exponentieller Backoff (1m, 5m, 15m, 1h) und Status-Markierung (failed, delivered).
- Einführung einer Tabelle
FLAW-08: Statische JWT-Tokens ohne Blacklisting
- Betroffene Komponenten:
architecture.md,internal/api/auth.go - Ursache: Nach dem Login wird ein JWT ausgestellt, das clientseitig im
localStoragegespeichert wird. Der Server prüft lediglich die kryptografische Signatur des Tokens, führt aber keine serverseitige Sitzungsliste. - Folgen: Wird einem Benutzer die Berechtigung entzogen oder ändert er sein Passwort, bleibt ein bereits ausgestellter Token bis zu seinem Ablaufdatum (z. B. 7 Tage) gültig.
- Empfohlene Lösung:
- Einführung einer
revoked_tokens-Tabelle oder einertoken_version-Spalte in derusers-Tabelle. - Implementierung von kurzlebigen Access-Tokens (15 Min.) in Verbindung mit rotierenden Refresh-Tokens in
httpOnly-Cookies.
- Einführung einer