docs(security): Design-Entscheidung D1 (Projekte teamweit geteilt) eintragen
Some checks are pending
security-baseline / secrets (push) Waiting to run
security-baseline / node (push) Waiting to run
security-baseline / semgrep (push) Waiting to run
security-baseline / trivy (push) Waiting to run

Julian hat am 17.10.2026 bestaetigt: die Software laeuft nur im eigenen
Team, alle Nutzer sehen alle Projekte. Bevor Externe Konten bekommen, wird
Besitz/Freigabe pro Projekt eingefuehrt. R3 (ASVS-light B1/B2) damit nicht
anwendbar; idorGap-Kommentar und AGENTS.md verweisen auf D1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Grovy311 2026-10-17 12:56:23 +02:00
parent 1cf406d216
commit e51752f3e1
3 changed files with 30 additions and 40 deletions

View file

@ -63,7 +63,7 @@ Gesundheitsdaten: keine. Personenbezug: nur Benutzerkonten.
|---|---|---|---|---|---|---|---|
| R1 | Zugriff ohne Anmeldung | A1, A2 | alle Projekte lesen/ändern | K | Auth-Guard vor allen `/api`-Routen, offen nur `login`/`status`/`logout` und `/health`; Passwortwechsel-Pflicht vor allem anderen | `tests/security/baseline.test.ts` „jede Route verlangt Login“, „gefälschte Identität wird abgelehnt“; `roles.test.ts` „ohne Passwortwechsel …“; Mutationen „Login-Pflicht entfernt“, „ungeschützte Route vor dem Auth-Guard“, „Passwortwechsel-Pflicht entfernt“ | belegt |
| R2 | Rechteausweitung `user` → `admin`, Benutzerverwaltung | A3 | Konten anlegen, Passwörter zurücksetzen, Admin werden | K | `/api/users/*` nur `admin`; Rolle kommt aus der DB, nie aus der Anfrage; zod `.strict()`; letzter aktiver Admin nicht entfernbar (atomar); Reset/Deaktivieren beendet alle Sessions | `roles.test.ts` „normaler Nutzer bekommt 403 …“ + Gegenprobe; `authentication.test.ts` „never removes the last active admin“, „… concurrent updates“; Baseline Mass Assignment (`role: "admin"`); Mutation „Admin-Rolle für /api/users entfernt“ | belegt |
| R3 | Fremde Projekte lesen, ändern, exportieren, löschen (ASVS-light **B1/B2**: Objektzugriff, Listen) | A4 | jeder angemeldete Nutzer hat Vollzugriff auf alle Projekte | K | **keine** Prüfung pro Projekt; Projekte haben keinen Besitzer. Gedacht als teamweit geteilte Projekte, aber **noch nicht als Entscheidung festgehalten** (E1) | IDOR-/Listen-Tests in `baseline.test.ts` laufen, zählen per `idorGap` (`tests/security/security.config.ts`) als TODO. `idorGap` allein ist kein Beleg | **offen, Entscheidung E1 ausstehend** |
| R3 | Fremde Projekte lesen, ändern, exportieren, löschen (ASVS-light **B1/B2**: Objektzugriff, Listen) | A4 | jeder angemeldete Nutzer hat Vollzugriff auf alle Projekte | K | keine Prüfung pro Projekt, Projekte haben keinen Besitzer: **Design-Entscheidung D1** (teamweit geteilt, Konten nur für das eigene Team) | D1 unten; IDOR-/Listen-Tests in `baseline.test.ts` laufen und zählen per `idorGap` (`tests/security/security.config.ts`) als TODO | **nicht anwendbar (D1)**, neu bewerten bei externen Konten |
| R4 | CSRF von fremden Seiten oder anderen Diensten | A1, A2 | Änderungen im Namen eines angemeldeten Nutzers | H | SameSite=Lax, Pflichtheader `X-Leistungsbilanz-Request: 1` für POST/PUT/PATCH/DELETE (erzwingt CORS-Preflight), `Sec-Fetch-Site` wenn vorhanden, keine CORS-Freigabe | `authentication.test.ts` „rejects writes without the application header …“; Baseline „CORS lässt fremde Origins nicht zu“; Mutationen „CSRF-Header-Prüfung entfernt“, „CORS für alle Origins“ | belegt |
| R5 | Passwort-Raten und Session-Diebstahl über das Internet | A1 | Konto übernommen, damit alle Projekte (R3) | H | scrypt mit Salt, 5 Fehlversuche je Name in 15 min → 429, gleiche Laufzeit für unbekannte Namen, Längengrenzen vor scrypt, neue Session-ID bei Login und Passwortwechsel, gleitende 12 h + harte 7 Tage, max. 20 Sessions je Nutzer. Bewusst nicht: dauerhafte Sperre, IP-basierte Drosselung (alle Anfragen kommen über den Rewrite mit derselben IP), 2. Faktor | `authentication.test.ts` (Hash, Throttle, Session-Laufzeit, Look-alike-Namen, Längengrenzen) | belegt; Restrisiko öffentliche Login-Seite siehe E2 |
| R6 | Klartext im LAN: Port 3090 auf `0.0.0.0`, HTTP am Caddy vorbei; Passwort und `lb_session` mitlesbar; über `http://192.168.0.133:3090` gesetzte Cookies gehen an alle HTTP-Dienste dieses Hosts | A2 | Session-Übernahme | H | Port nur für Caddy erreichbar machen (Bind/Firewall), Zugriff nur über die TLS-Hostnamen, `COOKIE_SECURE=true` | `/home/cj/leistungsbilanz/compose.local.yaml` (`${LB_WEB_PORT:-3090}:3001`); Wert von `COOKIE_SECURE` in der Live-`.env` nicht geprüft | **offen** |
@ -81,34 +81,26 @@ Gesundheitsdaten: keine. Personenbezug: nur Benutzerkonten.
Hier steht nur, was Julian bzw. Christian ausdrücklich entschieden haben. Ein Agent trägt hier
nichts als entschieden ein.
*(noch keine)*
**D1: Projekte sind teamweit geteilt (kein Besitz pro Projekt).**
- Entschieden von: Julian, am 17.10.2026 gegenüber Christian bestätigt (beantwortet E1).
Eingetragen von einem Agenten auf Anweisung von Christian.
- Begründung: Die Software läuft nur im eigenen Team. Alle angemeldeten Nutzer sollen alle
Projekte sehen und bearbeiten.
- Gilt für: jede Instanz, auf der nur Mitglieder des eigenen Teams Konten haben; heute die
Instanz auf cj-ki (`leistungsbilanz.jansseneconsult.de`, `leistungsbilanz.electric-consult.de`).
Admins legen Konten nur für Teammitglieder an, keine Externen.
- Neu bewerten: sobald Externe (z. B. Kunden, Subplaner) ein Konto bekommen sollen. Dann wird
das System überarbeitet (Besitz bzw. Freigabe pro Projekt), **bevor** das erste externe Konto
angelegt wird; `idorGap` entfernen, die IDOR-Tests müssen grün werden (`authz-idor-audit`).
- Folge: ASVS-light B1/B2 sind für diese Instanz nicht anwendbar (R3). `idorGap` in
`tests/security/security.config.ts` bleibt gesetzt und macht den Ist-Zustand sichtbar.
## Offene Entscheidungen
**E1 (K, blockiert den Release): Sind Projekte bewusst für alle angemeldeten Nutzer geteilt?**
Heute sieht und ändert jeder angemeldete Nutzer jedes Projekt (R3). Damit B1/B2 im
`pre-release-gate` als „nicht anwendbar“ gelten, braucht es unter „Design-Entscheidungen“ einen
Eintrag mit:
- **Wer** hat entschieden (Julian? Christian? beide?) und **wann**
- **Warum** (z. B. alle Nutzer arbeiten im selben Planungsteam an denselben Projekten,
Vertretung ohne Freigabeschritt)
- **Für welche Instanz und welches Team**: gilt das für `leistungsbilanz.jansseneconsult.de` und
`leistungsbilanz.electric-consult.de` (dieselbe Instanz auf cj-ki)? Wer bekommt dort Konten,
nur das eigene Team oder auch Externe (Kunden, Subplaner)?
- **Bis wann / unter welcher Bedingung** das neu bewertet wird (z. B. sobald externe Konten
dazukommen oder eine zweite Firma die Instanz nutzt)
Vorschlag für den Eintrag, **erst nach Bestätigung durch Julian** übernehmen:
> D1: Projekte sind teamweit geteilt (kein Besitz pro Projekt). Entschieden von <Name> am
> <Datum>. Begründung: <…>. Gilt für die Instanz <…> mit Konten nur für <Team>. Neu bewerten,
> sobald <Bedingung>. Folge: ASVS-light B1/B2 nicht anwendbar, `idorGap` in
> `tests/security/security.config.ts` bleibt bis dahin gesetzt.
Lautet die Antwort „nein“: Besitz bzw. Freigabe pro Projekt bauen, `idorGap` entfernen, die
IDOR-Tests müssen grün werden (`authz-idor-audit`).
E1 (geteilte Projekte) ist mit D1 beantwortet.
**E2 (H): Bleibt die Instanz ohne Authelia öffentlich erreichbar?** Laut Caddy-Kommentar seit
28.09.2026 bewusst so, wer das entschieden hat, steht dort nicht. Mit R3 heißt ein erratenes
28.09.2026 bewusst so, wer das entschieden hat, steht dort nicht. Wegen D1 heißt ein erratenes
Passwort Zugriff auf alle Kundenprojekte. Alternativen: Authelia bzw. VPN vorschalten oder
2. Faktor in der App.