ERSTEINRICHTUNG_GEHEIMNIS=<zufällig, npm run schluessel erzeugt einen>
VERTRAUE_PROXY=true
Beide sind nicht zwingend, beide haben Folgen, wenn sie fehlen:
Ohne ERSTEINRICHTUNG_GEHEIMNIS läuft alles weiter, nur der erste
Zugang ließe sich nicht anlegen. Auf einer bestehenden Installation ist
das richtig so; bei einer Neuinstallation braucht es den Wert.
Ohne VERTRAUE_PROXY=true zählt die Anmeldesperre nur noch nach
E-Mail-Adresse, nicht mehr zusätzlich nach Anschluss. Beim ersten Mal
steht eine erklärende Zeile im Log.
Nach dem Update, nur mit Objektspeicher
npm run speicher:verschluesseln # erst schauen
npm run speicher:verschluesseln -- --anwenden
Bereits abgelegte Dateien liegen unverschlüsselt, bis sie einmal
umgeschrieben werden. Lesen funktioniert durchgehend, der Lauf lässt sich
abbrechen und wiederholen.
Was geändert wurde
Drei Zusagen, die nicht galten
Der zweite Faktor war im Kopf von auth.ts als Pflicht beschrieben,
durchgesetzt aber nur im App-Layout. Server Actions und Downloads liefen
daran vorbei. Sitzt jetzt in erfordereBenutzer(); ein Test liest den
Quelltext und schlägt an, sobald eine Umgehung dazukommt.
middleware.ts: „Ohne Authentifizierungs-Cookie gar nicht erst
weiterreichen" — die Middleware prüft keine Anmeldung.
datei.ts: die Pfadprüfung fange „Symlink-Tricks" ab — resolve()
folgt keinem Link.
Anmeldung
Die Sperre verlängerte sich bei jedem abgewiesenen Versuch um die volle
Dauer. Wer alle zehn Minuten ein falsches Passwort an eine fremde
Adresse schickte, sperrte deren Inhaber dauerhaft aus — ohne das
Passwort zu kennen. Dazu eine Tabellenzeile je Anfrage.
TOTP-Codes galten bis zu 90 Sekunden mehrfach.
X-Forwarded-For wurde aus dem ersten Eintrag gelesen, den der
Aufrufer selbst schreibt, und auch ohne Proxy geglaubt.
trustHost stand fest auf true, obwohl AUTH_URL gesetzt ist.
Passkeys ließen sich ohne erneute Bestätigung anlegen und entfernen.
Die Ersteinrichtung stand zwischen erstem Start und erstem Zugang offen
und war nicht atomar.
Daten
Der Objektspeicher verließ sich auf ServerSideEncryption: 'AES256' —
den Schlüssel des Anbieters. Jetzt derselbe Umschlag wie beim
Dateitreiber, zusätzlich an den Ablageort gebunden. Presigned URLs
entfallen dadurch; Downloads laufen über die eigene Route, die die
Berechtigung prüft und protokolliert.
PDF-Ströme wurden ohne Obergrenze ausgepackt (Dekompressionsbombe), und
beim Rendern bestimmte die im Dokument angegebene Seitengröße die
Bildgröße.
Abhängigkeiten
32 Schwachstellen, davon 5 kritische → 0. Ohne Sprung auf Next.js 16: next, next-auth und @auth/core waren nur durchgereicht markiert; ein
eigenes Advisory hatten allein nodemailer, postcss und sharp.
Geprüft
tsc --noEmit
sauber
Lint
sauber
Unit-Tests
709
E2E
95 bestanden, 6 übersprungen (viewport-bedingt)
npm audit
0
Die neuen Tests sind gegen Mutationen geprüft — zweimal war der erste
Anlauf wertlos und blieb bei entferntem Schutz grün; beide wurden ersetzt.
Dazu ein Trockenlauf des Migrationsskripts über 120 Dateien und eine Probe
mit einer Klartextdatei, die danach verschlüsselt auf der Platte liegt und
sich korrekt zurücklesen lässt.
Zum Testen
git switch fix/38-sicherheitsaudit
npm install
npm run db:deploy
npm run dev
Anmelden — muss normal funktionieren.
Denselben TOTP-Code sofort ein zweites Mal eingeben → wird abgewiesen.
Unter Mein Konto einen Passkey anlegen wollen → fragt vorher nach
Passwort und Code.
Ein Dokument öffnen → lädt über die Anwendung, nicht mehr direkt vom
Speicher.
Arbeitet die Punkte aus der Sicherheitsprüfung ab und ergänzt einen
eigenen Durchgang entlang der OWASP Top 10.
Closes #38
## Vor dem Update: zwei neue `.env`-Zeilen
```bash
ERSTEINRICHTUNG_GEHEIMNIS=<zufällig, npm run schluessel erzeugt einen>
VERTRAUE_PROXY=true
```
Beide sind nicht zwingend, beide haben Folgen, wenn sie fehlen:
- Ohne `ERSTEINRICHTUNG_GEHEIMNIS` läuft alles weiter, nur der **erste**
Zugang ließe sich nicht anlegen. Auf einer bestehenden Installation ist
das richtig so; bei einer Neuinstallation braucht es den Wert.
- Ohne `VERTRAUE_PROXY=true` zählt die Anmeldesperre nur noch nach
E-Mail-Adresse, nicht mehr zusätzlich nach Anschluss. Beim ersten Mal
steht eine erklärende Zeile im Log.
## Nach dem Update, nur mit Objektspeicher
```bash
npm run speicher:verschluesseln # erst schauen
npm run speicher:verschluesseln -- --anwenden
```
Bereits abgelegte Dateien liegen unverschlüsselt, bis sie einmal
umgeschrieben werden. Lesen funktioniert durchgehend, der Lauf lässt sich
abbrechen und wiederholen.
## Was geändert wurde
**Drei Zusagen, die nicht galten**
- Der zweite Faktor war im Kopf von `auth.ts` als Pflicht beschrieben,
durchgesetzt aber nur im App-Layout. Server Actions und Downloads liefen
daran vorbei. Sitzt jetzt in `erfordereBenutzer()`; ein Test liest den
Quelltext und schlägt an, sobald eine Umgehung dazukommt.
- `middleware.ts`: „Ohne Authentifizierungs-Cookie gar nicht erst
weiterreichen" — die Middleware prüft keine Anmeldung.
- `datei.ts`: die Pfadprüfung fange „Symlink-Tricks" ab — `resolve()`
folgt keinem Link.
**Anmeldung**
- Die Sperre verlängerte sich bei jedem abgewiesenen Versuch um die volle
Dauer. Wer alle zehn Minuten ein falsches Passwort an eine fremde
Adresse schickte, sperrte deren Inhaber dauerhaft aus — ohne das
Passwort zu kennen. Dazu eine Tabellenzeile je Anfrage.
- TOTP-Codes galten bis zu 90 Sekunden mehrfach.
- `X-Forwarded-For` wurde aus dem **ersten** Eintrag gelesen, den der
Aufrufer selbst schreibt, und auch ohne Proxy geglaubt.
- `trustHost` stand fest auf `true`, obwohl `AUTH_URL` gesetzt ist.
- Passkeys ließen sich ohne erneute Bestätigung anlegen und entfernen.
- Die Ersteinrichtung stand zwischen erstem Start und erstem Zugang offen
und war nicht atomar.
**Daten**
- Der Objektspeicher verließ sich auf `ServerSideEncryption: 'AES256'` —
den Schlüssel des Anbieters. Jetzt derselbe Umschlag wie beim
Dateitreiber, zusätzlich an den Ablageort gebunden. Presigned URLs
entfallen dadurch; Downloads laufen über die eigene Route, die die
Berechtigung prüft und protokolliert.
- PDF-Ströme wurden ohne Obergrenze ausgepackt (Dekompressionsbombe), und
beim Rendern bestimmte die im Dokument angegebene Seitengröße die
Bildgröße.
**Abhängigkeiten**
32 Schwachstellen, davon 5 kritische → 0. Ohne Sprung auf Next.js 16:
`next`, `next-auth` und `@auth/core` waren nur durchgereicht markiert; ein
eigenes Advisory hatten allein `nodemailer`, `postcss` und `sharp`.
## Geprüft
| | |
|---|---|
| `tsc --noEmit` | sauber |
| Lint | sauber |
| Unit-Tests | 709 |
| E2E | 95 bestanden, 6 übersprungen (viewport-bedingt) |
| `npm audit` | 0 |
Die neuen Tests sind gegen Mutationen geprüft — zweimal war der erste
Anlauf wertlos und blieb bei entferntem Schutz grün; beide wurden ersetzt.
Dazu ein Trockenlauf des Migrationsskripts über 120 Dateien und eine Probe
mit einer Klartextdatei, die danach verschlüsselt auf der Platte liegt und
sich korrekt zurücklesen lässt.
## Zum Testen
```bash
git switch fix/38-sicherheitsaudit
npm install
npm run db:deploy
npm run dev
```
1. Anmelden — muss normal funktionieren.
2. Denselben TOTP-Code sofort ein zweites Mal eingeben → wird abgewiesen.
3. Unter *Mein Konto* einen Passkey anlegen wollen → fragt vorher nach
Passwort und Code.
4. Ein Dokument öffnen → lädt über die Anwendung, nicht mehr direkt vom
Speicher.
npm audit meldete 32 Schwachstellen, davon 5 kritische. Der Vorschlag
des Werkzeugs war ein Sprung auf Next.js 16 - noetig war er nicht: next,
next-auth und @auth/core waren nur durchgereicht markiert, ein eigenes
Advisory hatten allein nodemailer, postcss und sharp. Die drei sind
Blattpakete und lassen sich gezielt ueberschreiben.
Nodemailer 9 kollidiert mit einer optionalen Peer-Angabe von next-auth
(hoechstens 8). Das ist folgenlos, weil der E-Mail-Provider von next-auth
hier nicht benutzt wird, sondern Nodemailer direkt.
Danach: 0 Schwachstellen.
Refs #38
"Der zweite Faktor ist Pflicht" stand im Kopf von auth.ts, durchgesetzt
wurde es aber nur im App-Layout. Ein Layout schuetzt, was durch das
Layout gerendert wird - Server Actions und Route Handler laufen daran
vorbei. Wer eine Passwort-Sitzung hatte, die TOTP-Einrichtung aber nie
abgeschlossen hat, kam an die Daten.
Die Pruefung steckt jetzt in erfordereBenutzer() selbst und damit ueber
erfordereZugang() automatisch in jeder Server Action. Massgeblich ist
der Datenbankstand, nicht das Feld im Sitzungstoken - das laesst sich
ueber session.update() vom Client beeinflussen.
Fuer Route Handler gibt es benutzerFuerRoute(): 401 bzw. 403 statt einer
Umleitung. Eine Anmeldeseite als Inhalt einer Word-Datei waere die
falsche Antwort.
Dabei aufgefallen: Der Dokumentenabruf unter /api/dokumente/datei fragte
nur nach der Anmeldung. Auch geschlossen.
Ausnahmen sind aufgezaehlt und werden von tests/mfa-grenze.test.ts
festgehalten - der Test liest den Quelltext und schlaegt an, sobald eine
weitere dazukommt.
Refs #38
Die Sperre hielt sich selbst am Leben. Jeder Aufruf wurde festgehalten,
auch der, der wegen der laufenden Sperre gar nicht geprueft wurde; die
Frist lief zugleich ab dem juengsten Fehlversuch. Wer also alle zehn
Minuten ein falsches Passwort an eine fremde Adresse schickte, sperrte
deren Inhaber dauerhaft aus - ohne das Passwort zu kennen. Jeder dieser
Aufrufe hinterliess ausserdem eine Zeile, die erst der Wartungslauf nach
30 Tagen wieder los wurde.
Jetzt zwei getrennte Dinge: Ein geprueft fehlgeschlagener Versuch zaehlt
und wird einzeln festgehalten. Ein waehrend der Sperre abgewiesener
Aufruf zaehlt nicht und wird hoechstens einmal je Sperre vermerkt.
Die Auswertung geht die Versuche von alt nach neu durch und zieht die
Runden nach: Ist das Mass voll, beginnt an diesem Versuch eine Sperre
und der Zaehler faengt von vorn an. Damit laeuft die Frist auch dann ab,
wenn die Fehlversuche noch im Zeitfenster stehen - vorher blieb der
Zugang bis zum Fensterende dicht.
Die Tests sind gegen vier Mutationen geprueft; jede wird rot.
Refs #38
Zwei Felder fuer zwei Luecken, in einer Migration:
benutzer.totpLetzterSchritt haelt das zuletzt akzeptierte TOTP-Fenster
fest. Ohne das gilt ein Code bis zu 90 Sekunden lang beliebig oft.
Die Tabelle ersteinrichtung hat genau eine Zeile mit fester Id. Das ist
kein Protokoll, sondern eine Sperre: Bisher entschied "gibt es schon
einen Benutzer?" und danach wurde angelegt - zwei Anfragen gleichzeitig
kamen beide durch. Jetzt entscheidet der Primaerschluessel.
Enthalten ist eine Datenmigration: Wo schon ein Zugang besteht, wird die
Sperrzeile gesetzt. Ohne sie stuende die Einrichtungsseite auf jedem
bestehenden System nach dem Update wieder offen.
Die DROP-INDEX-Zeilen zu den Trigramm-Indizes hat das Skript aus #36
wieder entfernt; "npm run db:pruefen" bestaetigt, dass beide stehen.
Refs #38
Vier Stellen, an denen eine Zusage nicht gehalten wurde.
1. Ein TOTP-Code galt mehrfach. Der Kommentar in totp.ts versprach seit
dem Anfang, der Aufrufer speichere das getroffene Zeitfenster - getan
hat es niemand. Damit liess sich ein abgelesener Code innerhalb von 90
Sekunden noch einmal einloesen. Jetzt geht jede Pruefung durch
zweitenFaktorEinloesen(), und die Bedingung steht im WHERE von
updateMany: Zwei gleichzeitige Anmeldungen mit demselben Code treffen
nicht beide eine Zeile.
2. Der Anschluss kam aus dem ERSTEN Eintrag von "x-forwarded-for". Das
ist der Teil, den der Aufrufer selbst schreibt. Wer bei jeder Anfrage
eine andere Zahl hineinschrieb, sah fuer die Anmeldesperre wie tausend
Anschluesse aus - die Zaehlung war nicht nur wirkungslos, sondern
irrefuehrend. Jetzt von hinten gelesen, und nur wenn VERTRAUE_PROXY
gesetzt ist. Ohne Proxy ist der Kopf fremdbestimmt; dann lieber keine
Zaehlung nach Anschluss als eine, die eine Kopfzeile aushebelt. Beim
ersten Mal steht eine Zeile im Log, damit das Fehlen auffaellt.
3. trustHost stand fest auf true. Ist AUTH_URL gesetzt - der
dokumentierte Betrieb setzt es, Passkeys brauchen es -, gibt es keinen
Grund, die Adresse aus einer Kopfzeile von aussen abzuleiten.
AUTH_TRUST_HOST bleibt wirksam, damit die dokumentierte Einstellung
nicht stillschweigend uebergangen wird.
4. Passkeys liessen sich ohne erneute Bestaetigung anlegen und entfernen.
Ein Passkey ist ein vollwertiger Zugang, der einen Passwortwechsel
ueberlebt - an einem unbeaufsichtigten Rechner waren das zwei Klicks.
Jetzt dasselbe Vorzimmer wie beim Vollexport. Dabei musste
alsFehler() lernen, die Weiterleitung von Next durchzulassen: Sonst
verschwand die Zwischenfrage in "Der Passkey konnte nicht geprueft
werden".
Refs #38
Die Aktion ist die einzige Stelle der Anwendung, an der jemand ohne
Anmeldung schreiben darf. Zwei Dinge fehlten.
Erstens stand die Seite zwischen dem ersten Start und dem Anlegen des
Zugangs fuer jeden offen: Wer sie in diesem Fenster fand, wurde
Administrator einer Anwendung mit Gesundheits- und Finanzdaten. Das
Fenster ist kurz, aber Zufall ist keine Zugriffskontrolle. Jetzt muss
ERSTEINRICHTUNG_GEHEIMNIS aus der .env mit eingegeben werden; verglichen
wird ueber Hashes in gleichbleibender Zeit, und ein falscher Wert
antwortet mit derselben Mindestdauer wie das Anmeldeformular.
Fehlt der Wert, ist die Einrichtung gesperrt statt offen. Das ist
Absicht: Eine Installation, bei der die Zeile vergessen wurde, soll eine
Meldung zeigen und nicht einrichtbar sein.
Zweitens waren Pruefung und Anlegen zwei Schritte. Beide laufen jetzt in
einer Transaktion, und die Sperrzeile mit fester Id entscheidet - eine
zweite gleichzeitige Anfrage scheitert am Primaerschluessel, bevor ein
zweiter Administrator entsteht.
Die Seite liest die Sperrzeile mit: Lag der erste Zugang im Papierkorb,
stand Schritt 1 sonst wieder offen. Der Seed setzt sie ebenfalls.
Refs #38
Zwei Wege, auf denen ein hochgeladenes Dokument den Server umbringen
konnte. Beide haben gemeinsam, dass die Groesse aus der Datei kam.
1. Dekompressionsbombe. Die PDF-Stroeme wurden mit inflateSync ohne
maxOutputLength ausgepackt. Ein Flate-Strom sagt nicht, wie gross er
ausgepackt wird - ein paar Kilobyte Fuellung blaehen sich auf Gigabyte
auf. Jetzt ein Deckel je Strom (8 MB) und einer ueber die Summe
(64 MB); zlib bricht selbst ab, der Strom wird verworfen, der Text ist
eben unvollstaendig.
2. Riesenseiten. "pdftoppm -r 300" rendert mit fester Aufloesung, aber
die Seitengroesse steht im PDF. Bei 200 x 200 Zoll ergeben 300 dpi ein
Bild von 60000 x 60000 Pixeln, und zwar je Seite. Jetzt "-scale-to"
statt fester dpi: Die laengste Kante bekommt eine feste Pixelzahl,
unabhaengig davon, was im Dokument als Papierformat steht. Fuer A4
entspricht das etwa den bisherigen 300 dpi. Dazu ein Deckel auf die
Gesamtgroesse der gerenderten Dateien.
Der erste Bombentest war wertlos: Die Nutzlast enthielt keine
PDF-Textoperatoren, also war das Ergebnis mit und ohne Grenze leer. Mit
einer Marke in der Nutzlast und einer Gegenprobe knapp unter der Grenze
schlagen beide Mutationen an.
Refs #38
Im S3-Treiber stand nur ServerSideEncryption: 'AES256'. Das ist
Verschluesselung mit dem Schluessel des Anbieters - er entschluesselt beim
Ausliefern selbst. Gegen eine verlorene Platte im Rechenzentrum hilft das,
gegen den Anbieter und gegen gestohlene Zugangsdaten zum Eimer nicht. In
diesem Eimer liegen Bescheide, Arztbriefe und Kontoauszuege.
Der Dateitreiber hat es von Anfang an richtig gemacht. Der Umschlag zieht
deshalb nach src/lib/speicher/umschlag.ts um und wird von beiden benutzt,
statt ihn ein zweites Mal zu schreiben.
Neu ist die Bindung an den Ablageort: Der Schluessel des Objekts wandert
in die Zusatzdaten. Wer Schreibrechte auf den Eimer hat, kann die Bytes
von Dokument A damit nicht unter dem Namen von Dokument B ablegen - beim
Lesen schlaegt die Pruefung fehl, statt den falschen Bescheid mit
gueltiger Pruefsumme auszuliefern.
Damit fallen die presigned URLs weg. Sie liessen den Browser direkt beim
Anbieter laden; was dort liegt, ist jetzt Geheimtext. Die Auslieferung
geht ueber dieselbe eigene Route wie beim Dateitreiber - die prueft
zusaetzlich die Berechtigung und schreibt einen Audit-Eintrag. Ein
weitergeleiteter Link allein oeffnet also nichts mehr.
Bestehende Dateien bleiben durchgehend lesbar: Der Treiber erkennt am
Kopf, ob ein Umschlag da ist, und reicht alles andere unveraendert durch.
Auch die alten Umschlaege ohne Ortsbindung gehen weiter auf - sonst waere
die Verbesserung ein Datenverlust. Nachgezogen wird mit
npm run speicher:verschluesseln # nur schauen
npm run speicher:verschluesseln -- --anwenden
Geprueft: 12 Tests gegen drei Mutationen, ein Trockenlauf ueber die 120
Dateien der Entwicklungsablage, und eine Probe mit einer Klartextdatei,
die nach dem Lauf verschluesselt auf der Platte liegt und sich trotzdem
korrekt zurueeklesen laesst.
Refs #38
Zwei Kommentare versprachen Schutz, den es nicht gab. Das ist schlimmer
als eine fehlende Zusage, weil man sich darauf verlaesst:
- In middleware.ts stand "Ohne Authentifizierungs-Cookie gar nicht erst
weiterreichen". Die Middleware prueft keine Anmeldung, sie setzt
Kopfzeilen; die Liste steuert allein den Cache-Control-Kopf. Wer den
Satz glaubt, verzichtet an einer neuen Seite auf erfordereBenutzer().
Die Liste heisst jetzt OHNE_CACHE_SPERRE und sagt selbst, was sie ist.
- In datei.ts hiess es, die Pfadpruefung fange "Symlink-Tricks" ab.
resolve() rechnet mit Zeichenketten und folgt keinem Link - dafuer
braeuchte es realpath(). Nachgeruestet wird es nicht: Wer dort einen
Symlink anlegen kann, hat schon Schreibrechte im Datenverzeichnis. Der
Satz musste trotzdem weg.
Dazu docs/SICHERHEIT.md: Bedrohungsmodell, der Durchgang entlang der
OWASP Top 10 - und ein eigener Abschnitt darueber, was ausdruecklich NICHT
geschuetzt ist.
Aus dem OWASP-Durchgang kamen keine weiteren Funde. Zwei Stellen sahen
danach aus und waren es nicht: Prisma.join() in suche.ts fuegt nur bereits
parametrisierte Fragmente zusammen, und der Suchausschnitt mit den
<mark>-Markern aus ts_headline wird zerlegt und als React-Kinder
gerendert, nicht als HTML - obwohl die Quelle der OCR-Text hochgeladener
Dokumente ist.
Refs #38
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Arbeitet die Punkte aus der Sicherheitsprüfung ab und ergänzt einen
eigenen Durchgang entlang der OWASP Top 10.
Closes #38
Vor dem Update: zwei neue
.env-ZeilenBeide sind nicht zwingend, beide haben Folgen, wenn sie fehlen:
ERSTEINRICHTUNG_GEHEIMNISläuft alles weiter, nur der ersteZugang ließe sich nicht anlegen. Auf einer bestehenden Installation ist
das richtig so; bei einer Neuinstallation braucht es den Wert.
VERTRAUE_PROXY=truezählt die Anmeldesperre nur noch nachE-Mail-Adresse, nicht mehr zusätzlich nach Anschluss. Beim ersten Mal
steht eine erklärende Zeile im Log.
Nach dem Update, nur mit Objektspeicher
Bereits abgelegte Dateien liegen unverschlüsselt, bis sie einmal
umgeschrieben werden. Lesen funktioniert durchgehend, der Lauf lässt sich
abbrechen und wiederholen.
Was geändert wurde
Drei Zusagen, die nicht galten
auth.tsals Pflicht beschrieben,durchgesetzt aber nur im App-Layout. Server Actions und Downloads liefen
daran vorbei. Sitzt jetzt in
erfordereBenutzer(); ein Test liest denQuelltext und schlägt an, sobald eine Umgehung dazukommt.
middleware.ts: „Ohne Authentifizierungs-Cookie gar nicht erstweiterreichen" — die Middleware prüft keine Anmeldung.
datei.ts: die Pfadprüfung fange „Symlink-Tricks" ab —resolve()folgt keinem Link.
Anmeldung
Dauer. Wer alle zehn Minuten ein falsches Passwort an eine fremde
Adresse schickte, sperrte deren Inhaber dauerhaft aus — ohne das
Passwort zu kennen. Dazu eine Tabellenzeile je Anfrage.
X-Forwarded-Forwurde aus dem ersten Eintrag gelesen, den derAufrufer selbst schreibt, und auch ohne Proxy geglaubt.
trustHoststand fest auftrue, obwohlAUTH_URLgesetzt ist.und war nicht atomar.
Daten
ServerSideEncryption: 'AES256'—den Schlüssel des Anbieters. Jetzt derselbe Umschlag wie beim
Dateitreiber, zusätzlich an den Ablageort gebunden. Presigned URLs
entfallen dadurch; Downloads laufen über die eigene Route, die die
Berechtigung prüft und protokolliert.
beim Rendern bestimmte die im Dokument angegebene Seitengröße die
Bildgröße.
Abhängigkeiten
32 Schwachstellen, davon 5 kritische → 0. Ohne Sprung auf Next.js 16:
next,next-authund@auth/corewaren nur durchgereicht markiert; eineigenes Advisory hatten allein
nodemailer,postcssundsharp.Geprüft
tsc --noEmitnpm auditDie neuen Tests sind gegen Mutationen geprüft — zweimal war der erste
Anlauf wertlos und blieb bei entferntem Schutz grün; beide wurden ersetzt.
Dazu ein Trockenlauf des Migrationsskripts über 120 Dateien und eine Probe
mit einer Klartextdatei, die danach verschlüsselt auf der Platte liegt und
sich korrekt zurücklesen lässt.
Zum Testen
Passwort und Code.
Speicher.
Im S3-Treiber stand nur ServerSideEncryption: 'AES256'. Das ist Verschluesselung mit dem Schluessel des Anbieters - er entschluesselt beim Ausliefern selbst. Gegen eine verlorene Platte im Rechenzentrum hilft das, gegen den Anbieter und gegen gestohlene Zugangsdaten zum Eimer nicht. In diesem Eimer liegen Bescheide, Arztbriefe und Kontoauszuege. Der Dateitreiber hat es von Anfang an richtig gemacht. Der Umschlag zieht deshalb nach src/lib/speicher/umschlag.ts um und wird von beiden benutzt, statt ihn ein zweites Mal zu schreiben. Neu ist die Bindung an den Ablageort: Der Schluessel des Objekts wandert in die Zusatzdaten. Wer Schreibrechte auf den Eimer hat, kann die Bytes von Dokument A damit nicht unter dem Namen von Dokument B ablegen - beim Lesen schlaegt die Pruefung fehl, statt den falschen Bescheid mit gueltiger Pruefsumme auszuliefern. Damit fallen die presigned URLs weg. Sie liessen den Browser direkt beim Anbieter laden; was dort liegt, ist jetzt Geheimtext. Die Auslieferung geht ueber dieselbe eigene Route wie beim Dateitreiber - die prueft zusaetzlich die Berechtigung und schreibt einen Audit-Eintrag. Ein weitergeleiteter Link allein oeffnet also nichts mehr. Bestehende Dateien bleiben durchgehend lesbar: Der Treiber erkennt am Kopf, ob ein Umschlag da ist, und reicht alles andere unveraendert durch. Auch die alten Umschlaege ohne Ortsbindung gehen weiter auf - sonst waere die Verbesserung ein Datenverlust. Nachgezogen wird mit npm run speicher:verschluesseln # nur schauen npm run speicher:verschluesseln -- --anwenden Geprueft: 12 Tests gegen drei Mutationen, ein Trockenlauf ueber die 120 Dateien der Entwicklungsablage, und eine Probe mit einer Klartextdatei, die nach dem Lauf verschluesselt auf der Platte liegt und sich trotzdem korrekt zurueeklesen laesst. Refs #38