diff --git a/BEFUNDE.md b/BEFUNDE.md new file mode 100644 index 0000000..6467b28 --- /dev/null +++ b/BEFUNDE.md @@ -0,0 +1,154 @@ +# Bestandsaufnahme, 12.09.2026 + +Einmal durch die ganze Cloud: Code, Sicherheit, offene Enden. Geordnet nach +dem, was passieren kann — nicht nach Aufwand. + +Was hier steht, ist **gemessen**, nicht geschätzt: Routen aus `main.go` +extrahiert und gegen die Rechteprüfungen in den Handlern abgeglichen, +`govulncheck` über den ganzen Baum, Zeilen und Testumfang je Paket gezählt. + +--- + +## 1 · Ein Bild-Upload kann den Prozess töten + +**Schwere: hoch. Jeder Kontoinhaber, normale Bedienung.** + +`cmd/aether/main.go:702` erzeugt das Vorschaubild in einer **nackten +Goroutine**: + +```go +go func() { + … + thumbnails.Generate(ctx, st, …) // dekodiert eine hochgeladene Datei +}() +``` + +Chis `Recoverer` hängt am Anfragebehandler. Eine Panik in einer *eigenen* +Goroutine fängt er nicht — sie nimmt den **ganzen Go-Prozess** mit. Docker +startet ihn neu (`restart: unless-stopped`), aber jeder gerade laufende Upload +stirbt mit, und wer die Datei noch einmal hochlädt, wiederholt das Spiel. + +Die passende Lücke war offen: **GO-2026-5061**, Panik im WebP-Dekodierer bei +unstimmiger Alphakanal-Größe. Behoben am 12.09.2026 durch `x/image` 0.42 → 0.45 +(`ef60497`). + +**Damit ist die Hälfte erledigt, nicht das Problem.** Die Bauweise bleibt: Der +nächste Dekodierer-Fehler trifft genauso, und Bilddekodierer sind eine Gattung, +in der regelmäßig etwas gefunden wird. Vier weitere Goroutinen dieser Art gibt +es: `internal/sharing/handler.go:232` und `main.go:185` (beide nur Datenbank, +harmlos), `internal/gc/gc.go:257`, und der OCR-Arbeiter (`go worker.Run(ctx)`), +der Fremdprogramme aufruft und deren Ausgabe liest. + +**Vorschlag:** Ein kleines `sicher(func())` in `pkg/middleware` oder daneben, +das `recover()` macht und die Panik protokolliert, statt sie durchzulassen — +und alle Hintergrund-Goroutinen darüber starten. Fünf Zeilen, eine Stelle. + +> Der Unterschied ist wichtig: Die Version zu heben schließt **diese** Lücke. +> Der `recover` schließt die **Gattung**. + +--- + +## 2 · Beschädigte Downloads werden erkannt und verschwiegen + +**Schwere: mittel. Datenintegrität, still.** + +`internal/files/handler.go:496` rechnet beim Ausliefern die Prüfsumme mit — +genau so, wie es die Projektregel verlangt („SHA-256: beim Upload berechnen, +beim Download verifizieren"). Nur passiert bei einer Abweichung nichts: + +```go +actual := hex.EncodeToString(h256.Sum(nil)) +if actual != f.Checksum { + // Log corruption; headers already sent, can't change status. + // TODO: alert / observability hook + _ = actual +} +``` + +Kein Logeintrag, kein Zähler, keine Meldung. Der Kommentar sagt, was fehlt. + +Dass der Statuscode nicht mehr zu ändern ist, stimmt — die Kopfzeilen sind +längst draußen. Aber **still** muss es deshalb nicht sein. Ein `log.Error()` +mit Datei-ID, erwarteter und tatsächlicher Summe kostet nichts und ist der +Unterschied zwischen „ich habe eine kaputte Datei" und „ich habe eine kaputte +Datei und weiß es seit drei Monaten nicht". + +Zweitens ließe sich die Verbindung abbrechen, statt sie sauber zu beenden: Der +Browser zeigt dann einen abgebrochenen Download statt einer heilen Datei mit +falschem Inhalt. Das ist die ehrlichere Ausgabe. + +**Vorschlag:** Logeintrag jetzt, Abbruch dazu. Beides zusammen unter zehn Zeilen. + +--- + +## 3 · Vier Pakete ohne einen einzigen Go-Test + +**Schwere: mittel. Nicht was heute kaputt ist, sondern was morgen still kaputtgeht.** + +| Paket | Zeilen | Test | Aussenpruefung | +|---|---:|---:|---| +| `internal/tags` | 1132 | **0** | keine | +| `internal/documents` (OCR, Volltext) | 1008 | **0** | keine | +| `internal/sharing` | 382 | **0** | Python-Selbsttest, 24 Prüfungen | +| `internal/rescan` | 479 | **0** | Python-Selbsttest, 20 Prüfungen | +| `internal/audit` | 207 | **0** | keine | +| `internal/thumbnails` | 113 | **0** | keine | + +Bei `sharing` und `rescan` deckt wenigstens ein Selbsttest die Außenseite ab. +Bei **`documents` und `tags` gar nichts** — und `documents` ist das Stück, das +aus `scan_0042.pdf` ein auffindbares Dokument macht. Genau der Grund, aus dem +Aether das Rechnungsarchiv ist. + +**Vorschlag:** Nicht auf Abdeckung zielen, sondern auf die Stellen, an denen +ein Fehler still bleibt: Beim OCR die Warteschlange (Versuche, Aufgeben, +Wiederaufnahme nach hartem Abbruch), bei den Etiketten die Rechteprüfung — +Etiketten sind je Konto, und das prüft heute nichts nach. + +--- + +## 4 · Was in Ordnung ist + +Der Vollständigkeit halber, weil eine Liste nur aus Mängeln ein falsches Bild +gibt: + +* **Alle 27 Admin-Routen prüfen serverseitig auf Adminrecht.** Zwei + Ausnahmen sind Absicht und dokumentiert: `GET /admin/settings` liest jeder + Angemeldete (Theming — und es liegen dort nur sechs Branding-Schlüssel, + nachgesehen), `GET /ocr` ist der eigene Erkennungsstand ohne Adminrecht. +* **Kein einziges zusammengesetztes SQL.** Alles parametrisiert. +* **Inline-Auslieferung nur über eine Positivliste** harmloser Typen, mit + `sandbox`, und `allow-scripts` ausschließlich für PDF. +* **Ratenbegrenzung an jeder unangemeldeten Tür**, die eines braucht: Anmeldung + (20/min), WebDAV, CalDAV, Freigabelinks (je 10 Fehlversuche / 15 Minuten). + Die übrigen offenen Wege (`/health`, `/branding`, `/manifest.webmanifest`, + `/api/v1/public/config`) geben nichts preis. +* **Kein `dangerouslySetInnerHTML`, kein `eval`** im Frontend. +* **Sechs Selbsttest-Suiten, 220 Prüfungen** gegen die laufende Instanz, dazu + 37 Frontend-Tests und die Go-Tests. + +--- + +## 5 · Kleinere offene Enden + +* **`internal/groups` ist ein leerer Stub** (0 Zeilen). Gruppenordner liegen in + `files/groupfolder.go`. Entweder das Verzeichnis weg oder der Code dorthin — + ein leeres Paket im Baum ist eine Frage, die jeder einmal stellt. +* **`favicon_storage_key`** steht als leere Zeile in den Einstellungen; die + Funktion heißt seit dem App-Symbol anders. Schadet nichts, wird ignoriert. +* **Die Fehlversuchssperren liegen im Arbeitsspeicher.** Ein Neustart des + Containers hebt jede Sperre auf. Für den Hausgebrauch vertretbar; wer es + anders will, braucht eine Tabelle. +* **Frontend ohne Komponententests.** `vitest` läuft jetzt (37 Tests), aber nur + über reine Funktionen — für DOM-Tests fehlt `jsdom` in den devDependencies. + +--- + +## Reihenfolge, die ich vorschlagen würde + +1. **`recover` in die Hintergrund-Goroutinen** (Punkt 1, Rest). Klein, und es + schließt eine Gattung statt eines Einzelfalls. +2. **Prüfsummen-Abweichung melden** (Punkt 2). Zehn Zeilen für eine Auskunft, + die es sonst nie gibt. +3. **Tests für `documents`** (Punkt 3). Das Paket, dessen stilles Versagen am + teuersten ist: Die Suche findet dann einfach nichts, und das sieht aus wie + „ist nicht drin".