security(rescan): Notbremse vor dem harten Löschen
internal/rescan ist das einzige Stück Aether, das hart löscht: DELETE FROM files, kein Papierkorb. Entfernt wird, was beim Durchgang über die Platte nicht gesehen wurde. Der Ernstfall ist nicht ausgedacht. LocalFS.Walk gibt für ein nicht vorhandenes Verzeichnis nil zurück – kein Fehler, keine Einträge. Genau der Fingerabdruck eines Bind-Mounts, der nach einem Neustart nicht hochkam, oder eines Tippfehlers in DATA_PATH. Dann gilt jede Datei als verwaist, und ein einziger Aufruf mit ?anwenden=true löscht den gesamten Bestand aus der Datenbank. Die Bytes lägen noch da, aber niemand wüsste mehr, dass es sie gibt – und der GC räumte sie 24 Stunden später ab. Der GC hat diese Bremse seit dem 12.09. Beide gleichen Platte und Datenbank ab, nur aus entgegengesetzter Richtung, und beide haben denselben Fehlerfall: Eine Seite antwortet fälschlich mit „leer". Der GC löscht dann Blobs – die Datenbank weiss hinterher noch, was fehlt. Hier fällt die Zeile selbst. Die gefährlichere Seite war die ungesicherte. Zwei Bremsen: Die Notbremse (nichts gefunden, Datenbank kennt Dateien) lässt sich nicht übersteuern. Die Mengenbremse (mehr als die Hälfte) verlangt &bestaetigt=true. Die leere Installation ist ausgenommen. Dazu umgebaut: erst sammeln, dann entscheiden, dann löschen. Vorher wurde mitten in der Schleife gelöscht; die Gesamtzahl kennt man dort erst, wenn die Hälfte schon weg ist. Der Bericht nennt den Grund und ob er bestätigbar ist – die Oberfläche soll keinen Knopf anbieten, der nichts bewirkt. Ein abgebrochener Lauf steht rot in der Karte und im Audit-Log; gerade der, denn er ist der Hinweis darauf, dass mit dem Datenordner etwas nicht stimmt. Fünf Tests, unter Mutation nachgewiesen. Der Ernstfall läuft am echten Gerät: fünf Zeilen in der Datenbank, ein Wurzelverzeichnis ohne users/ – nach Apply stehen alle fünf noch, auch mit Bestätigung. audit und thumbnails bekommen bewusst keine Go-Tests; die Begründung steht in BEFUNDE.md statt als ewig offener Punkt. Nebenbefund behoben: kalender-teilen-selbsttest.py leckte je Lauf ein App-Passwort des Besitzers. Nach achtzehn Läufen war maxAppPasswords = 20 erreicht, und zwei Suiten scheiterten mit 409, ohne dass am Code etwas kaputt war. Geprüft: go build/vet sauber, alle Go-Pakete grün, 243 Prüfungen der sieben Selbsttest-Suiten grün, 48 Vitest-Tests, tsc sauber. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6fab2388ca
commit
662c0ebdc1
8 files changed
+391
-20
No files matched your search
+63
@@ -1562,6 +1562,69 @@ bleibt `node` (schnell); wer ein DOM braucht, schreibt
|
||||
|
||||
---
|
||||
|
||||
## Der Abgleich bekommt eine Notbremse (26.09.2026)
|
||||
|
||||
`internal/rescan` ist das einzige Stueck Aether, das **hart** loescht:
|
||||
`DELETE FROM files`, kein Papierkorb. Entfernt wird, was beim Durchgang ueber
|
||||
die Platte nicht gesehen wurde.
|
||||
|
||||
Der Ernstfall ist nicht ausgedacht ([localfs.go:199](../backend/internal/storage/localfs.go)):
|
||||
|
||||
if _, err := os.Stat(base); os.IsNotExist(err) {
|
||||
return nil // kein Fehler, keine Eintraege
|
||||
}
|
||||
|
||||
Existiert das Datenverzeichnis nicht, meldet `Walk` **keinen Fehler** – es
|
||||
meldet gar nichts. Genau der Fingerabdruck eines Bind-Mounts, der nach einem
|
||||
Neustart nicht hochkam, oder eines Tippfehlers in `DATA_PATH`. Dann gilt jede
|
||||
Datei als verwaist, und **ein** Aufruf mit `?anwenden=true` loescht den
|
||||
gesamten Bestand aus der Datenbank. Die Bytes laegen noch da, aber niemand
|
||||
wuesste mehr, dass es sie gibt – und der GC raeumte sie 24 Stunden spaeter ab.
|
||||
|
||||
Der GC hat diese Bremse seit dem 12.09. Beide gleichen Platte und Datenbank
|
||||
ab, nur aus entgegengesetzter Richtung, und beide haben denselben Fehlerfall:
|
||||
Eine Seite antwortet faelschlich mit "leer". Der GC loescht dann Blobs – die
|
||||
Datenbank weiss hinterher noch, was fehlt. Hier faellt die Zeile selbst. **Die
|
||||
gefaehrlichere Seite war die ungesicherte.**
|
||||
|
||||
### Zwei Bremsen, eine davon nicht uebersteuerbar
|
||||
|
||||
| Bremse | wann | bestaetigbar |
|
||||
|---|---|---|
|
||||
| **Notbremse** | nichts auf der Platte gefunden, Datenbank kennt Dateien | **nein** |
|
||||
| **Mengenbremse** | mehr als die Haelfte wuerde fallen | ja, mit `&bestaetigt=true` |
|
||||
|
||||
Die leere Installation (nichts hier, nichts dort) ist ausgenommen – da gibt es
|
||||
nichts zu verwechseln.
|
||||
|
||||
Dazu umgebaut: erst sammeln, dann entscheiden, dann loeschen. Vorher wurde
|
||||
mitten in der Schleife geloescht; die Gesamtzahl kennt man dort erst, wenn die
|
||||
Haelfte schon weg ist, und eine Bremse kaeme zu spaet.
|
||||
|
||||
- [x] Fuenf Tests in `bremse_test.go`. Unter Mutation nachgewiesen: Wird die
|
||||
Notbremse entfernt, werden `TestNotbremseBeiLeeremSpeicher` **und** der
|
||||
Ernstfalltest rot.
|
||||
- [x] Ernstfall am echten Geraet: fuenf Zeilen in der Datenbank, ein
|
||||
Wurzelverzeichnis ohne `users/`. Nach `Apply` stehen alle fuenf noch –
|
||||
auch mit ausdruecklicher Bestaetigung.
|
||||
- [ ] Im Admin-Bereich: "Rescan storage" -> Check -> Apply bei heilem Bestand
|
||||
arbeitet wie vorher 🔎
|
||||
- [ ] Ein abgebrochener Lauf steht rot in der Karte, mit Grund, und die Karte
|
||||
bleibt im Zustand "geprueft" (der Anwenden-Knopf verschwindet nicht)
|
||||
- [ ] Ein abgebrochener Lauf steht im Audit-Log – gerade der: Er ist der
|
||||
Hinweis darauf, dass mit dem Datenordner etwas nicht stimmt
|
||||
|
||||
### Nebenbefund: der Teilen-Selbsttest leckte App-Passwoerter
|
||||
|
||||
`kalender-teilen-selbsttest.py` legte je Lauf zwei App-Passwoerter an und
|
||||
raeumte nur das des Empfaengers weg (das faellt mit dessen Konto). Das des
|
||||
Besitzers blieb liegen. Nach **achtzehn** Laeufen war
|
||||
`auth.maxAppPasswords = 20` erreicht, und caldav wie kalender-teilen
|
||||
scheiterten mit `409` – ohne dass am Code etwas kaputt gewesen waere.
|
||||
|
||||
Aufgeraeumt und behoben; zweimal hintereinander laufen lassen, danach stehen
|
||||
wieder nur die beiden echten (`CalDAV`, `DAV`).
|
||||
|
||||
## Freigabelinks: Prüfsumme, Gruppenordner, Grenzen (25.09.2026)
|
||||
|
||||
Beim Lesen von `internal/sharing` fiel eine ungleiche Zusage auf: Der
|
||||
|
||||
Reference in new issue
Block a user