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

@ -798,16 +798,15 @@ Rules for agents working on this repo:
Open security points (state at adoption, 2026-10-15; updated 2026-10-17):
- **Threat model: `docs/THREATMODEL.md`** (German, since 2026-10-17). Keep it current when
auth, roles, routes, imports/exports or deployment change. Its "Design-Entscheidungen"
section is still empty: only Julian or Christian record decisions there, agents never do.
- **Shared-projects decision pending (THREATMODEL R3 / E1, blocks release).** B1/B2 (object
access, list filtering) are K items in the ASVS-light checklist. `pre-release-gate` reports
No-Go until E1 is answered and recorded as a design decision (who decided, when, why, which
instance/team, when to re-evaluate). `idorGap` alone is not evidence.
- **Projects have no owner.** Every signed-in user can read and change every project.
The IDOR tests in `baseline.test.ts` run but are reported as TODO via `idorGap` in
`security.config.ts`. If E1 is answered "no": introduce per-project ownership/sharing,
then remove `idorGap`; the tests must turn green.
auth, roles, routes, imports/exports or deployment change. Only Julian or Christian record
decisions in its "Design-Entscheidungen" section; agents only write down what they state.
- **Projects are shared team-wide by design (THREATMODEL D1, Julian, 2026-10-17).** Every
signed-in user can read and change every project, because the software runs only within
the own team. ASVS-light B1/B2 are therefore not applicable (R3). The IDOR tests in
`baseline.test.ts` run but are reported as TODO via `idorGap` in `security.config.ts`.
- **Before any external account (customers, subcontractors) is created:** introduce
per-project ownership/sharing, remove `idorGap`, the tests must turn green. D1 no longer
holds once externals get accounts.
- Further open items from the threat model (no decision needed, see its "Nächste Schritte"):
R6 live port 3090 reachable over plain HTTP in the LAN, R7 global 25 MB JSON limit before
the auth guard, R8 no XLSX zip-bomb guard before `workbook.xlsx.load`, R11 no route test

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.

View file

@ -94,12 +94,11 @@ const config = {
return (await res.json()) as Record<string, unknown>;
},
// OFFENE LÜCKE: Projekte haben keinen Besitzer, jeder angemeldete Nutzer sieht und
// bearbeitet alle Projekte. Gedacht als teamweit geteilt, die Entscheidung (wer, warum,
// welche Instanz/welches Team) ist aber noch nicht festgehalten: docs/THREATMODEL.md R3
// und offene Entscheidung E1. Bis dahin gilt B1/B2 im pre-release-gate als offen.
// Die IDOR-Tests laufen trotzdem und zeigen den Ist-Zustand, zählen aber als TODO statt
// als Fehler. Wird Besitz bzw. Freigabe pro Projekt eingeführt, diese Zeile entfernen,
// BEWUSSTE LÜCKE (Design-Entscheidung D1 in docs/THREATMODEL.md, Julian, 17.10.2026):
// Projekte haben keinen Besitzer, jeder angemeldete Nutzer sieht und bearbeitet alle
// Projekte, weil die Software nur im eigenen Team läuft. Die IDOR-Tests laufen trotzdem
// und zeigen den Ist-Zustand, zählen aber als TODO statt als Fehler. Bevor Externe ein
// Konto bekommen: Besitz bzw. Freigabe pro Projekt einführen, diese Zeile entfernen,
// dann müssen sie grün werden.
idorGap: "Projekte sind für alle angemeldeten Nutzer freigegeben (kein Besitz pro Projekt)",
idorCases: [