Zwei Fehler, beide erst im Browser sichtbar:
1. /docs blieb weiss. FastAPI holt Swagger von cdn.jsdelivr.net, und die
in Phase 3 eingeführte CSP (script-src 'self') blockiert das zu Recht.
Die Dateien liegen jetzt unter app/static/swagger/ im Repository - das
funktioniert auch ohne Internet und ruft keinen fremden Server auf, aus
demselben Grund, aus dem die Kantone-App ihre Schriften lokal ausliefert.
Das Favicon holte FastAPI ebenfalls von aussen; auch das ist jetzt lokal.
2. Die Wurzel / lieferte ein nacktes 404-JSON - im Browser sieht das aus
wie eine kaputte Anwendung. Jetzt eine schlichte Platzhalter-Seite, bis
die Galerie in Phase 4 steht.
Ausserdem beim Testen aufgefallen: eine lokale .env-Datei reicht
ADMIN_PASSWORD auch dann herein, wenn die Umgebungsvariable gelöscht wird -
bei pydantic-settings haben Umgebungsvariablen Vorrang, ein leerer Wert
überstimmt die Datei, ein Löschen nicht. Der Test setzt jetzt leer statt zu
löschen; im Betrieb ist das dieselbe Falle.
Der neue Test prüft, dass /docs keinen einzigen Verweis auf einen fremden
Host enthält - genau das würde sonst unbemerkt wieder hereinrutschen.
61 Tests, alle grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
Lesen darf jeder, schreiben nur der Betreiber - mit der bewussten Ausnahme
des Reservierens. Dazu Dockerfile und Compose-Datei, damit sich das lokal
ausprobieren lässt.
Beim Bauen sind zwei Fehler aufgefallen, die ohne Test nicht aufgefallen
wären:
1. slowapi zählt pro URL-Pfad. Weil jedes Kleidungsstück eine eigene URL
hat, bekam jedes seinen eigenen Zähler - ein Skript hätte also den
gesamten Bestand reservieren können, ohne je an ein Limit zu stossen.
Genau der Missbrauch, gegen den das Limit gedacht ist. Behoben mit
shared_limit und festem scope.
2. Der erste Anlauf des Tests machte fünf Anfragen gegen ein Limit von
fünf und konnte damit gar nichts zeigen. Geprüft wird jetzt der
tatsächlich ausgelieferte Standardwert, mit mehr Anfragen als erlaubt.
Weiter umgesetzt:
- Passwort als bcrypt-Hash aus der Umgebung, einmal beim Start gebildet
und gemerkt. Bei jeder Anfrage neu gehasht liesse sich die Anwendung
sonst ohne Anmeldung lahmlegen - bcrypt ist absichtlich langsam.
- Ohne hinterlegtes Passwort bleibt der Erfassungsbereich gesperrt (503)
statt offen zu stehen. Kein mitgeliefertes Standardpasswort.
- Sitzung als signiertes Cookie, HttpOnly, SameSite=Lax (blockt
seitenfremde POSTs), Secure abschaltbar nur fürs lokale Testen,
Abmeldung nach zwei Stunden Ruhe.
- CSP mit script-src 'self', nosniff, frame-ancestors none, dazu noindex
und robots.txt: die Galerie ist frei zugänglich, soll aber nicht
dauerhaft im Suchindex stehen.
- Der Betreiber darf Reservierungen ohne Token aufheben, damit sich eine
Missbrauchswelle aufräumen lässt.
- uvicorn mit --proxy-headers: hinter einem Reverse-Proxy zählte sonst
alles auf dessen IP, und ein einzelner Besucher sperrte alle aus.
- Container läuft nicht als root; Code gehört root, nur Daten und Bilder
dem Dienstbenutzer.
Umgebungsvariablen heissen jetzt ausdrücklich englisch (ADMIN_PASSWORD,
SECRET_KEY, ...), passend zur Anleitung und zur Kantone-App.
59 Tests, alle grün. Zusätzlich gegen den laufenden Container geprüft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o