Files
kanban/docs/audits/2026-05-19-repo-audit.md
2026-05-19 23:47:11 +02:00

4.9 KiB

Repo-Audit

Datum: 2026-05-19 Scope: vollstaendiges Repo Status: solide mit Risiken

Kurzfazit

  • Kritischste Themen: persistente HTML-/Script-Injection ueber innerHTML, unsichere Markdown-Link-Behandlung, Funktionsabweichungen bei Reset und Task-Erzeugung

Reproduzierbarkeit

Vor dem Audit wurden die verfuegbaren automatisierten Checks ueber ./scripts/verify-repo.sh ausgefuehrt.

Findings

  1. [High] Persistente HTML-/Script-Injection ueber unescaped User-State Datei: index.html:1505-1513, index.html:1621-1627, index.html:2084-2120, index.html:2298-2313 Problem: Mehrere Renderer schreiben benutzerkontrollierte Werte direkt per innerHTML ins DOM, ohne sie vorher zu escapen, z. B. task.t, idea.text, Board-Namen und Gruppennamen. Diese Werte kommen aus Inputs, contenteditable, prompt() und localStorage. Warum es zaehlt: Ein Eintrag wie <img src=x onerror=...> wird persistent gespeichert und bei jedem Render erneut ausgefuehrt. Empfehlung: Alle benutzerkontrollierten Texte konsequent mit escapeHtml() behandeln oder DOM-Knoten per textContent/createElement aufbauen statt HTML-Strings zu interpolieren.

  2. [High] Markdown-Renderer erlaubt javascript:-Links Datei: index.html:1953-2017, insbesondere index.html:2007 Problem: Der Markdown-Parser escaped zwar HTML, uebernimmt aber Link-Ziele ungeprueft in <a href="$2" target="_blank">. Ein Markdown-Link wie [x](javascript:alert(1)) bleibt klickbar. Warum es zaehlt: Jede geladene .md-Datei kann so aktiven Script-Code hinter einem scheinbar normalen Link verstecken. Empfehlung: Link-Protokolle whitelisten (http:, https:, ggf. file:) und alles andere verwerfen oder als reinen Text rendern. Zusaetzlich rel="noopener noreferrer" setzen.

  3. [High] Reset setzt nicht auf DEFAULTS zurueck, sondern nur die Spalten Datei: SPEC.md:112, index.html:1012-1023 Problem: Laut Spec soll ↺ Reset das aktuelle Board auf DEFAULTS zuruecksetzen. Implementiert wird aber nur cols = deepClone(DEFAULTS[curId].cols). Name, Goal, WIP-Limit, Throughput, SLE, Focus und overview bleiben erhalten. Warum es zaehlt: Der sichtbare UX-Vertrag stimmt nicht mit dem Verhalten ueberein; ein Reset hinterlaesst versteckte Altzustaende. Empfehlung: Das gesamte Default-Board zurueckkopieren und danach nur group/color/ring aus BOARD_META wieder anwenden.

  4. [Medium] Aus Ideen erzeugte Tasks bekommen kein movedAt Datei: SPEC.md:137-138, index.html:1343-1349, index.html:2252-2260 Problem: Beim Droppen einer Idee auf ein Board oder beim Promote-Dialog wird nur { t: idea.text } angelegt. movedAt fehlt, obwohl es laut Datenmodell Grundlage fuer Aging ist. Warum es zaehlt: Diese Tasks altern nie sichtbar und verfaelschen die Board-Signale gegenueber regulaer angelegten Karten. Empfehlung: Beim Erzeugen aus Ideen dieselbe Initialisierung wie in addCard() verwenden, inklusive movedAt = Date.now().

  5. [Medium] "Maskierte" Secrets liegen im Klartext in localStorage und im DOM Datei: SPEC.md:52, index.html:906-964, index.html:1740-1746 Problem: Der Secrets-Bereich ist nur optisch maskiert. Die Werte werden vollstaendig in kanban_v2 gespeichert und zusaetzlich als data-value/data-copy ins DOM geschrieben. Warum es zaehlt: Bei XSS, gemeinsam genutztem Browser-Profil oder lokalem Zugriff sind die Daten sofort auslesbar; die UI suggeriert mehr Schutz als tatsaechlich vorhanden ist. Empfehlung: Keine echten Secrets in App-State speichern. Stattdessen nur Env-Referenzen/Keys-Namen anzeigen oder Secrets separat ausserhalb des Boards halten.

  6. [Medium] State-Laden ist unvalidiert und schluckt Fehler vollstaendig Datei: index.html:855-856, index.html:863-864, index.html:905-939, index.html:2202-2203 Problem: localStorage-Payloads werden ohne Shape-Validierung uebernommen, und Exceptions werden mit leeren catch {}/catch(e) {} verschluckt. Schon eine kaputte Struktur kann das Laden teilweise abbrechen, ohne Hinweis fuer den Nutzer. Warum es zaehlt: Beschaedigter oder zukuenftiger inkompatibler State fuehrt zu stillen Datenverlusten oder schwer erklaerbaren Resets. Empfehlung: Geladene Daten vor der Uebernahme validieren, defekte Teile gezielt verwerfen und einen sichtbaren Recovery-Hinweis anzeigen.

Test- und Pruefstand

  • Tests: nicht ausgefuehrt, kein Test-Setup im Repo vorhanden
  • Lint/Typecheck/Build: nicht ausgefuehrt, kein entsprechendes Setup vorhanden
  • Shell-Syntax: bash -n server.sh erfolgreich
  • Nicht verifiziert: echtes Browser-Laufzeitverhalten, Drag-and-drop im UI, Verhalten unter beschaedigtem localStorage

Empfohlene Reihenfolge

  1. XSS-Pfade in Renderern schliessen
  2. Markdown-Link-Whitelist ergaenzen
  3. resetCurrentBoard() auf echten Default-Reset umstellen
  4. Task-Erzeugung vereinheitlichen (addCard, Idea-Drop, Promote)
  5. Secrets-Handling entschaerfen
  6. localStorage-Load validieren und Fehler sichtbar machen