docs: Bestandsaufnahme 12.09.2026
Einmal durch die ganze Cloud, geordnet nach dem, was passieren kann. Gemessen statt geschaetzt: Routen aus main.go extrahiert und gegen die Rechtepruefungen abgeglichen, govulncheck ueber den ganzen Baum, Zeilen und Testumfang je Paket gezaehlt. Drei Befunde, ein Abschnitt darueber, was in Ordnung ist (eine Liste nur aus Maengeln gibt ein falsches Bild), und eine vorgeschlagene Reihenfolge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ef604973c8
commit
89f0817dcb
1 file changed
+154
+154
@@ -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".
|
||||
Reference in new issue
Block a user