Irrefuehrende Sicherheitszusagen entfernen, OWASP-Durchgang festhalten

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
This commit is contained in:
Florian Wilde
2026-08-24 09:53:43 +02:00
parent 92ff8efe39
commit e48eda4332
3 changed files with 196 additions and 5 deletions
+12 -2
View File
@@ -34,8 +34,18 @@ function wurzel(): string {
/**
* Baut den Dateipfad - und prueft danach noch einmal, dass er wirklich
* unterhalb des Ablageverzeichnisses liegt. Der Schluessel ist zwar bereits
* streng geprueft, aber diese zweite Sperre kostet nichts und faengt auch
* Symlink-Tricks und Windows-Eigenheiten ab.
* streng geprueft, aber diese zweite Sperre kostet nichts.
*
* Was sie NICHT abfaengt, obwohl hier lange das Gegenteil stand: Symlinks.
* resolve() rechnet nur mit Zeichenketten und folgt keinem Link - dafuer
* braeuchte es realpath(). Ein untergeschobener Symlink im Ablageverzeichnis
* wuerde also nicht auffallen.
*
* Nachgeruestet wird das trotzdem nicht, und zwar aus einem Grund und nicht
* aus Bequemlichkeit: Wer dort einen Symlink anlegen kann, hat bereits
* Schreibrechte im Datenverzeichnis des Servers - dann ist der Umweg ueber
* einen Link die Sorge, die am wenigsten draengt. Der Satz musste
* verschwinden, weil er eine Sicherheit versprach, die es nie gab.
*/
function pfadFuer(schluessel: string): string {
const geprueft = schluesselErfordern(schluessel)
+16 -3
View File
@@ -30,8 +30,21 @@ import { NextResponse, type NextRequest } from 'next/server'
* Data-URI eingebettet, statt sie ueber einen Fremddienst zu holen.
*/
/** Ohne Authentifizierungs-Cookie gar nicht erst weiterreichen. */
const OEFFENTLICHE_PFADE = [
/**
* Seiten, die auch ohne Anmeldung sinnvoll im Verlauf stehen duerfen.
*
* ACHTUNG - was diese Liste NICHT ist: eine Zugriffskontrolle. Frueher
* stand hier "Ohne Authentifizierungs-Cookie gar nicht erst weiterreichen",
* und das war schlicht falsch: Die Middleware prueft keine Anmeldung, sie
* setzt Kopfzeilen. Ausgewertet wird die Liste einzig fuer die Frage, ob
* "Cache-Control: no-store" gesetzt wird.
*
* Der Irrtum waere teuer geworden: Wer den Satz glaubt, verzichtet an einer
* neuen Seite auf erfordereBenutzer() - "die Middleware faengt das ja ab".
* Sie faengt nichts ab. Der Zugriffsschutz sitzt in
* src/lib/auth/sitzung.ts, und nur dort.
*/
const OHNE_CACHE_SPERRE = [
'/anmelden',
'/ersteinrichtung',
'/datenschutz',
@@ -79,7 +92,7 @@ export function middleware(anfrage: NextRequest) {
// Verhindert, dass ein Bericht oder Beleg im Verlauf des Browsers
// zwischengespeichert wird.
if (!OEFFENTLICHE_PFADE.some((pfad) => anfrage.nextUrl.pathname.startsWith(pfad))) {
if (!OHNE_CACHE_SPERRE.some((pfad) => anfrage.nextUrl.pathname.startsWith(pfad))) {
antwort.headers.set('Cache-Control', 'no-store, must-revalidate')
}