Files
tessera-ctl/.planning/quick/261009-p0m-sicherheitsprotokoll-und-ci-sicherheitsp/261009-p0m-SUMMARY.md
T
schalli d62e6c2dbe
Tessera CI/CD / Lint & Type Check (push) Successful in 53s
Tessera CI/CD / Tests (push) Failing after 2m14s
Tessera CI/CD / Desktop-Pakete bauen (push) Has been skipped
Tessera CI/CD / Build & Publish Images (push) Has been skipped
Tessera CI/CD / Sicherheitspruefung (nur Bericht) (push) Has been skipped
docs(quick-261009-p0m): Sicherheitsprotokoll und CI-Pruefungen
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 21:07:46 +02:00

306 lines
43 KiB
Markdown

---
phase: quick-261009-p0m
plan: 01
status: complete
tasks_done: [1, 2, 3, 4, 5, 6]
plan_head_before: d15a470c5abed0c08a37ef098ea0e0b8c7676a43
plan_head_after: b0858896573693f06c2fa17e065470ab864fbbeb
commits: 9
actuals:
tasks: 6
commits: 9
---
# Quick 261009-p0m: Sicherheitsprotokoll + CI-Sicherheitspruefungen -- Summary
## Task 1: Tracer -- Job `security` als reiner Bericht (erledigt)
**Commit:** `3d00be8` ci(quick-261009-p0m): Sicherheitspruefung als reiner Bericht in der Pipeline (nicht gepusht)
Dateien im Commit: `.gitea/scripts/security-scan.sh` (neu, ausfuehrbar), `.gitleaks.toml` (neu), `.semgrepignore` (neu), `.gitignore`, `.dockerignore`, `.gitea/workflows/ci.yml` (nur Anhaengen: Kopfzeile + Job `security`, keine andere Zeile geaendert), `docs/ci-cd-setup.md` (§4 fuenf Jobs, Unterabschnitt, §6-Eintrag).
Nicht eingecheckt (Vorgabe des Orchestrators, alles unter `.planning/` bleibt unversioniert): `baseline/.gitignore`, `baseline/SUMMARY.txt` (Block "nach Ausnahmelisten"), `checks/task1-runner-full.txt`, diese Datei.
### Gates (alle gruen, Sentinel `task1 ok` gedruckt)
- ci.yml per js-yaml geparst: Jobs `quality,test,desktop,publish,security`; `needs: publish`, `continue-on-error: true`, **`timeout-minutes: 30` (Auflage des Plan-Checkers, Gate erweitert)**, Bedingung main/Tags v*, kein `secrets.` im Job, kein anderer Job haengt an `security`, jeder `run` endet auf `|| true`, `fetch-depth: 0`, `upload-artifact@v3` mit `continue-on-error`.
- `--print-plan`: main -> `api:beta`/`web:beta`, `v9.9.9` -> `api:live`, Zweig `live` -> `scan-image keine ...`.
- `.gitleaks.toml` strukturell (tomllib): `useDefault`, jede Liste mit Beschreibung; nur das Fixture-Verzeichnis als Ordner, alles andere einzelne verankerte Datei mit `targetRules`; `de.json` zusaetzlich `condition=AND` + `regexTarget=line` + Regex auf die eine Zeile `"authMethodPassword": "Benutzer/Passwort"`.
- gitleaks ueber die volle Historie (1369 Commits inkl. neuem Commit): **0 Treffer** (vorher 20).
- Voller Lauf im echten Runner-Abbild `gitea/runner-images:ubuntu-latest` (Netz `gitea`, Socket-Mount, frisches Werkzeug-Volume, Wegwerf-Klon mit Kanarienvogel-Token): rc=0, gitleaks `findings=1` (nur Kanarienvogel, github-pat), trivy-fs `secrets=1` (nur Kanarienvogel), alle uebrigen Werkzeuge und beide Abbilder gezaehlt -> Annahme A1 bestaetigt (Job-Container scannt Abbilder des Host-Daemons). Semgrep per pipx im Container installiert und gelaufen.
- Offline-Lauf (`--network none`, schreibgeschuetzter Klon): rc=0, 12 `skipped reason=`-Zeilen (>= 7 verlangt).
- Host-Installation: gitleaks 8.30.1, trivy 0.75.0, osv-scanner 2.6.0 liegen in `~/.cache/tessera-security` (Semgrep dort uebersprungen: kein pipx). pnpm 9.15.0 aus dem PATH.
- Wegwerf-Klon, Volume und root-eigene Berichte entfernt; Token steht in keiner Datei ausserhalb des Klons (geprueft: `ghp_` in der Nachweisdatei = 0 Treffer).
### Zahlen nach Ausnahmelisten (Details in `baseline/SUMMARY.txt`)
gitleaks 0; pnpm audit --prod C5 H73 M68 L5; osv-scanner 56 (pnpm-lock + Cargo.lock; Grundlinie 43 nur pnpm-lock); Semgrep 34 Treffer (ERROR 2, WARNING 29, MEDIUM 3) / 4 Scanfehler (PartialParsing, 0 Zeitueberschreitungen), vorher 58/59; trivy-fs C5 H73 M70 L5, misconfig 2, secrets 0; trivy-image api C6 H116 M99 L7, web C2 H19 M22 L1.
## Deviations from Plan
1. **[Orchestrator-Vorgabe] Nichts unter `.planning/` eingecheckt.** Der Plan nennt `baseline/.gitignore`, `baseline/SUMMARY.txt` und `checks/task1-runner-full.txt` als Commit-Dateien; sie liegen auf der Platte, bleiben aber unversioniert. Gates lesen sie von der Platte und sind gruen.
2. **[Rule 2 / Plan-Checker] `timeout-minutes: 30`** am Job ergaenzt (im Plan nicht vorgesehen), Gate und `docs/ci-cd-setup.md` (Unterabschnitt + §6) sind angepasst.
3. **Trivy-Ausnahme** fuer die Testschluessel liegt als zur Laufzeit erzeugte `trivy-secret.yaml` (allow-rule auf den Fixture-Ordner) im Arbeitsordner des Skripts, nicht als Repository-Datei; wirkt nur auf den Secret-Scanner (trivy-fs secrets 7 -> 0, Kanarienvogel weiterhin gefunden).
4. Der Skript-Aufruf `all` rechnet Semgrep-Zeitlimit pro Regel/Datei `--timeout 20` (Plan: "per-file timeout"); Semgrep kennt nur dieses eine Limit.
## Hinweise fuer Task 2 (Protokoll)
- Zahlen "roh" aus `baseline/SUMMARY.txt` (oberer Teil) und "nach Ausnahmelisten" (unterer Block "nach Ausnahmelisten (2026-10-09, Task 1)"). Unterschied osv: 43 (nur pnpm-lock) vs. 56 (mit Cargo.lock). Semgrep roh 58 -> 34; die 4 Scanfehler sind alle PartialParsing (ci.yml, beide Dockerfiles), keine Zeitueberschreitungen mehr.
- Abbild-Zaehlung des Skripts summiert OS- und Node-Pakete (api M99, web M22; Forschung nannte M98/M21 nur Node + je 1 Alpine).
- Neue Dateien: `.gitleaks.toml` (6 Ausnahmegruppen), `.semgrepignore`; ci-cd-setup.md beschreibt Version-Anheben-Rezept (Version + URL + SHA256 gemeinsam).
- Hostwerkzeuge liegen bereit (Precondition von Task 2 erfuellt: `git log -- .gitea/scripts/security-scan.sh` zeigt 3d00be8; `gitleaks version` -> 8.30.1).
- Erster echter CI-Lauf steht aus (Orchestrator/Nutzer-Checkliste nach dem Push): Job `security` muss gelb/gruen laufen, `SECURITY-SUMMARY`-Zeilen erscheinen, `upload-artifact@v3` ist best effort (Annahme A4 ungeprueft). Der Skript-Lauf ist bis auf den Artefakt-Schritt im Runner-Abbild nachgestellt.
- Plattenstand nach Task 1: 80 % (36 GB frei), unter der 85-%-Schwelle.
- Der Eintrag `semgrep ... skipped reason=kein-pipx` auf dem Host ist erwartet.
## Task 2: Sicherheitsprotokoll, Verweise, Pflegeregel, CHANGELOG (erledigt)
**Commit:** `5b36b9a` docs(quick-261009-p0m): Sicherheitsprotokoll mit Bestandsaufnahme und erster Vollpruefung (nicht gepusht)
Dateien im Commit: `docs/sicherheitsprotokoll.md` (neu, 227 Zeilen), `docs/README.md` (Absatz "Ebenfalls dabei"), `docs/anleitung-betrieb.md` (Satz im Kopf + Absatz in Kapitel 8 "Automatische Sicherheitspruefung"), `docs/anleitung-entwicklung.md` (Inhaltsverzeichnis 9 = Sicherheitspruefungen, 10 = Konventionen; neuer Abschnitt mit "So pflegen Sie dieses Protokoll", "Die Pruefung in der Pipeline", "Pruefungen von Hand wiederholen", "Ausnahmelisten"), `CHANGELOG.md` (ein Bullet "Sicherheit: ..." unter Neu; keine vierte Gruppe).
Nicht eingecheckt (Vorgabe): `checks/link-check.py` (liegt auf der Platte), diese Datei.
### Gates (Sentinel `task2 ok` gedruckt)
- Alle sieben Ueberschriften exakt, alle 20 Schluesselwoerter, keine Du-Form, kein "Lizenz", kein "Mandant" im Protokoll (strenger als verlangt), " Sie " vorhanden.
- gitleaks `dir` auf das Protokoll: keine Funde. Link-Pruefer: 8 Links geprueft, 0 kaputt (Anker `#sicherheitsprüfungen` aufgeloest).
- CHANGELOG: nur Neu/Geaendert/Behoben, Bullet "- Sicherheit: ... Sicherheitsprotokoll ...".
### Inhalt des Protokolls (fuer die Folgeaufgaben wichtig)
- Abschnitte: Auf einen Blick, So lesen Sie, Wie die Pruefungen arbeiten (Unterabschnitte je Werkzeug), Bisherige Sicherheitspruefungen (Tabelle der acht Pruefungen, Haertung Adressschutz 09.10., Bedrohungsbetrachtungen 179 Plaene / 884 Kennungen frisch ausgezaehlt, Datentrennung als bestehender Schutz ohne Schaltzustand, Tabelle weiterer Auftraege, Fehlerregister, Tests, "Was es bisher nicht gab"), Erste vollstaendige Pruefung (Tabelle roh / nach Ausnahmelisten), Einordnung der Befunde (23 Zeilen, alle Abhaengigkeits-Zeilen noch "offen"), Verlauf (5 Eintraege, neueste oben).
- **Task 3** muss ergaenzen: Unterabschnitt "Aussenpruefung (ZAP)" unter "Wie die Pruefungen arbeiten", Verlauf-Eintrag fuer den ersten ZAP-Lauf (oben), "Auf einen Blick", Kapitel 9 der Betriebsanleitung. Das Protokoll nennt den Alpha-Hostnamen bewusst noch nirgends.
- **Tasks 4/5/6** muessen Stand-Spalte der Zeilen von "offen" auf "behoben (Datum)" setzen: Next.js, proxy-addr, handlebars (Paket entfernt), nodemailer, undici, multer, adm-zip 0.6.1, "Uebrige Bausteine", Entwicklungswerkzeuge im api-Abbild, npm im web-Abbild, rustls, AES-GCM-Tag; Zeilen xlsx, node-forge, glib bleiben offen; Abschnitt "Stand nach der Behebung" mit den Zahlen der Abschlussmessung; "Auf einen Blick" und Verlauf nachziehen. Die Zeile "Fehlende Gesundheitspruefung web" steht auf "offen".
- Wahrheitsgehalt der Bestandsaufnahme (aus den Dateien/Git nachgeprueft): Phase 08 Fix in `eebceb2` (CR-01, CR-02, WR-01..05, IN-01 behoben; IN-02/IN-03 weiter offen, im Code bestaetigt); Phase 16 vier Warnungen behoben, IN-01/IN-02 offen (im Code bestaetigt); Phase 18 vier behoben, zwei Hinweise nicht bearbeitet; dts IN-01/IN-02/IN-04 uebersprungen (Datenbankaenderung/Entwurf noetig); dkv alle 11 inkl. EXTRA behoben; ikt alle 15 behoben. Summe 89 Befunde: 76 behoben, 13 Hinweise offen. Fehlerregister: 39 Eintraege, 25 behoben, 1 zurueckgestellt, 13 offene gehoeren alle zur Datentrennung zwischen Organisationen und werden im Protokoll nur in einem Satz erwaehnt (D-07).
- Triage-Fakten, die ich gegen die Rohdaten geprueft habe: adm-zip-Meldung ohne Korrektur ist "Mittel" (Symlinks beim Entpacken); node-forge-Meldung betrifft die Pruefung von Unterschriften; xlsx: Prototype-Pollution und ReDoS; `trust proxy` wird im Quelltext nirgends gesetzt.
## Deviations (Task 2)
Keine fachliche Abweichung. Kleine Anmerkung: Der Plan nennt `.planning/` nicht als Commit-Ort fuer `link-check.py`; er bleibt gemaess Orchestrator-Vorgabe unversioniert und das Gate liest ihn von der Platte. Die Anhaengsel-Zeile der Anlage des Commits nutzt wie verlangt die Opus-Zeile als Trailer.
## Hinweise fuer Task 3
- Hostwerkzeuge liegen bereit. Das Protokoll wartet auf den ZAP-Teil (siehe oben). `link-check.py` kann nach jeder Aenderung der Dokumente wiederholt werden (`python3 -I .planning/quick/261009-p0m-sicherheitsprotokoll-und-ci-sicherheitsp/checks/link-check.py`).
- Beim Ergaenzen von Kapitel 9 der Betriebsanleitung den Anker-Stil beibehalten: Querverweise im Protokoll zeigen auf `anleitung-entwicklung.md#sicherheitsprüfungen`.
- Wortregeln fuer das Protokoll beibehalten (Gate von Task 2 prueft sie nicht fuer Task 3): Sie-Form, kein "Lizenz", kein "Mandant", keine internen IPs.
## Task 3: ZAP-Grundpruefung (erledigt, mit Abweichung beim Ziel)
**Commit:** `13571df` ci(quick-261009-p0m): ZAP-Grundpruefung gegen alpha vor Freigaben, erster Lauf (nicht gepusht)
Dateien im Commit: `.gitea/scripts/zap-baseline.sh` (neu, ausfuehrbar), `.gitea/scripts/zap-hooks.py` (neu), `docs/sicherheitsprotokoll.md`, `docs/anleitung-betrieb.md` (Kapitel 9, Schritt 2 "Pruefung von aussen" vor "Zusammenfuehren und taggen"), `docs/anleitung-entwicklung.md` (Unterabschnitt "Pruefung von aussen (ZAP)"), `CHANGELOG.md` (Sicherheits-Bullet um die Aussenpruefung ergaenzt). Nicht eingecheckt (Vorgabe): `checks/zap-testserver.py`, `checks/task3-zap-local.txt`, `checks/zap-alpha-2026-10-09.txt`, `baseline/SUMMARY.txt` (ZAP-Zeile angehaengt), diese Datei. `baseline/zap-hook-basic-auth.py` geloescht.
### Abweichung (Orchestrator-Entscheidung)
Die oeffentliche Adresse von alpha war vom Dev-Host nicht erreichbar (Zeitueberschreitung auf Port 443, 3 Versuche; allgemeines Internet ging). Auf Anweisung des Orchestrators wurde der erste Lauf **direkt gegen die Anwendung auf alpha ohne Proxy** ausgefuehrt (interne Adresse, Port 3000, `/login` = 200, Version per `/api-proxy/health/version`: `v1.10.1-80-gd15a470`, Kanal beta). Das Skript hat das Ziel als Parameter `ZAP_TARGET` (Standard weiter die oeffentliche Adresse). Im Protokoll steht nirgends die IP, nur "interne Adresse des Testservers"; das Protokoll sagt ausdruecklich, dass der Lauf ueber die oeffentliche Adresse noch folgt. Das Gate wurde angepasst (ZAP-SUMMARY-Zeile mit beliebigem Ziel statt festem Hostnamen; zusaetzliche Pruefungen: keine private IP in den Docs, keine Du-Form/Lizenz/Mandant, gitleaks auf dem Protokoll). Sentinel `task3 ok` gedruckt.
### Lokaler Beweis (Passivitaet)
Testserver mit POST-Formular, ZAP ueber die Docker-Bruecke, `ZAP_SPIDER_MINUTES=1`: `lauf-ohne-anmeldung POST=0 GET=8`; `lauf-mit-anmeldung POST=0 auth_ja=8 auth_nein=1` (die eine Anfrage ohne Kopf ist die Vorabpruefung des Skripts). Auch der Hakenweg fuer die Anmeldedatei funktioniert. Beim Test fiel auf und wurde behoben: bei vorzeitigem Abbruch (SIGPIPE) blieben die temporaeren Dateien mit dem Kopf liegen, deshalb wird die Umgebungsdatei jetzt sofort nach dem Container-Lauf entfernt und die Falle faengt auch HUP/PIPE ab (beide Reste von /tmp geloescht). Der Bericht enthaelt bei Nutzung der Anmeldedatei die gesendeten Kopfzeilen samt Zugangsdaten (in Skript-Kopf und Entwicklungsanleitung vermerkt; Berichte liegen nur in `security-reports/`, ignoriert).
### Erster Lauf gegen alpha (2026-10-09 19:41, Ziel: Anwendung direkt, 28 URLs, 0 POST)
ZAP-SUMMARY: **hoch 0, mittel 2, niedrig 6, info 3** (11 Meldungsarten).
- Mittel: fehlende Content-Security-Policy; fehlender Schutz gegen Einrahmen (Clickjacking) -- beide auf /login, /reset-password.
- Niedrig: Cross-Origin-Embedder-/Opener-/Resource-Policy fehlen; Permissions-Policy fehlt; X-Content-Type-Options fehlt; X-Powered-By (Next.js-Standard).
- Info: Content-Type fehlt (Umleitungen 307 fuer nicht angemeldete Besucher), nicht speicherbar, speicherbar.
- Einordnung im Protokoll: Kopfzeilen = offen ("Aufgabe des Proxys, beim oeffentlichen Lauf erneut pruefen; sonst in der Anwendung setzen"; `next.config.ts` setzt keine Kopfzeilen und kein `poweredByHeader: false`), HTTPS/HSTS = nicht beurteilt, offen (Proxy), Infos = bewusst akzeptiert. Es wurde kein Tessera-Code und keine Proxy-Konfiguration geaendert.
## Hinweise fuer Task 4 und folgende
- Protokoll: neue Tabelle "Pruefung von aussen gegen alpha" und fuenf neue Zeilen in "Einordnung der Befunde" (Kopfzeilen mittel/niedrig, X-Powered-By, HTTPS/Proxy, Infos); Verlaufseintrag steht oben. Task 6 soll "Auf einen Blick" (Bullet "Pruefung von aussen") und diese Zeilen nachziehen; ein spaeterer Lauf ueber die oeffentliche Adresse waere eine weitere Tabellenzeile + Verlaufseintrag.
- Angebot an den Nutzer (nicht ausgefuehrt, kein Auftrag): `poweredByHeader: false` und Sicherheits-Kopfzeilen in `apps/web/next.config.ts` waeren eine kleine, sichere Haertung; vorher klaeren, ob der Proxy sie schon setzt.
- `link-check.py` laeuft gruen (11 Links). Hostwerkzeuge und das ZAP-Abbild (3.8 GB, per Digest) liegen lokal; Plattenstand 80 %.
## Task 4: Abhaengigkeiten innerhalb der Hauptversionen (erledigt)
**Commit:** `cf982e9` fix(quick-261009-p0m): Abhaengigkeiten innerhalb der Hauptversionen aktualisiert, zwei unbenutzte entfernt (nicht gepusht)
Dateien im Commit: `package.json` (neu: `pnpm.overrides`, 15 Eintraege), `pnpm-lock.yaml`, `apps/api/package.json`, `apps/web/package.json`, `apps/api/src/mail/mail.module.ts` und `apps/api/src/calendar/providers/exchange.provider.ts` (nur falsch gewordene Kommentare), `apps/desktop/src-tauri/Cargo.lock`, `docs/sicherheitsprotokoll.md`, `docs/anleitung-entwicklung.md` (neuer Abschnitt `### Abhängigkeiten aktualisieren`), `CHANGELOG.md` (dritter "Sicherheit:"-Bullet unter Behoben). Nicht eingecheckt (Vorgabe): `checks/mail-smoke.sh`, `checks/task4-audit.txt`, diese Datei.
### Audit vorher / nachher (`checks/task4-audit.txt`)
| | critical | high | moderate | low | gesamt |
|---|---|---|---|---|---|
| prod vorher | 5 | 73 | 68 | 5 | 151 |
| prod nachher | **0** | **9** | 3 | 0 | 12 |
| alle vorher | 7 | 85 | 74 | 5 | 171 |
| alle nachher | 2 | 9 | 5 | 0 | 16 |
Verbleibend prod (12): xlsx 2 hoch (keine Korrektur), node-forge 1 hoch (keine Korrektur), sharp 3 hoch (Korrektur erst 0.35 = andere 0.x-Minor), deepmerge-ts 1 hoch (Prisma-Werkzeugkette, Korrektur erst v8), nodemailer 2 hoch + 3 mittel (Korrektur erst in nodemailer 10 = neue Hauptversion). Verbleibend nur dev: tinypool 1.1.1 (2 kritisch) + vitest/@vitest/mocker 3.2.6 der API (2 mittel), Korrektur jeweils nur in neuer Hauptversion (vitest 4 / tinypool 2).
### Was geaendert wurde
- Entfernt: `@nestjs-modules/mailer`, `ews-javascript-api` (vorher per grep nachgewiesen: kein Import in `apps/`; Exchange bleibt httpntlm + rohes SOAP). 232 Paketnamen entfallen aus dem Lockfile.
- Direkt angehoben: next ^15.5.27 (installiert 15.5.27); @nestjs/common/core/platform-express ^11.2.7 (multer 2.4.0); nodemailer ^9.1.1; undici exakt 7.30.0; adm-zip ^0.6.1; csv-parse ^7.0.3; vitest (web) ^4.1.11 (noetig, weil @vitest/mocker < 4.1.11 gemeldet).
- Overrides (alle innerhalb derselben Hauptversion, 15 Stueck): baseline-browser-mapping, brace-expansion (1.x und 5.x), browserslist 4, fast-uri 3, ip-address 10, js-yaml 4, nanoid 3, nodemailer 9, postcss 8, proxy-addr 2, qs 6, source-map-js 1, underscore 1, undici 7 (auf 7.30.0, damit nur eine undici-Kopie im Baum bleibt). Selektorform `name@>=N <N+1` (bzw. `<2` fuer brace-expansion 1.x) wirkt; per Lockfile geprueft.
- Prisma weiter 6.19.3, Next 15, NestJS 11, React 19.2.7.
- rustls 0.23.41 -> 0.23.45; `cargo update -p rustls --precise` zog zusaetzlich `rustls-webpki` 0.103.13 -> 0.103.15 mit. `cargo check` und `cargo clippy` fehlerfrei, keine Warnungen.
- Nichts musste zurueckgepinnt werden (kein Baustein als "offen wegen Bruch").
### Neue-Namen-Pruefung (T-p0m-SC)
Vergleich der Paketnamen der `packages:`-Sektion vor/nach: **0 neue Namen**, 232 entfallen. Kein Vater noetig. (Neue Versionen vorhandener Namen sind keine neuen Namen.)
### Gates (Sentinel `task4 ok` gedruckt, Kette aus dem Plan wortgetreu)
frozen-Lockfile-Install, Versionspruefung (pakete ok), rustls 0.23.45, cargo check/clippy, `pnpm type-check`, `pnpm lint`, API-Tests 169 Dateien / 3573 Tests, Web-Tests 160 Dateien / 1945 Tests, Audit nachher (0 kritisch, 9 < 73 hoch), `docker compose up -d --build --wait api web`, `/login` = 200, e2e-changelog, e2e-preload-api, e2e-cert all, nc-test-setup, e2e-files, e2e-transfer, e2e-shares all, mail-smoke (`mail ok`), Anleitungs-Ueberschrift, >= 3 "Sicherheit:"-Bullets. Link-Pruefer: 11 Links, 0 kaputt.
## Deviations (Task 4)
1. **[Rule 1] `checks/mail-smoke.sh` liest die E-Mail-Adresse aus `GET /users`, nicht aus `/auth/me`.** `/auth/me` liefert kein E-Mail-Feld (Plan-Annahme falsch); `/users` (als admin) liefert es. Der Test vorher auf dem unveraenderten Stack gruen (`mail ok`), danach ebenfalls; Mailhog `total` waechst.
2. **Rate-Limit beim Rerun:** Ein zweiter Lauf von `e2e-shares.sh` innerhalb weniger Minuten laeuft in das Anlegelimit (`tooManyShares`, 429, 320 s). Kein Fehler im Code; nach 5,5 Minuten Pause lief die Kette komplett gruen. Bei jedem Wiederholen: erst warten.
3. **vitest (web) ^4.1.11** zusaetzlich zu den im Plan genannten Bumps (Audit meldete @vitest/mocker unter 4.1.11); Minor-Bump im selben Major, alle Web-Tests gruen.
4. Protokoll in Task 4 laut Plan aktualisiert (Verlauf, Zeilen in "Einordnung der Befunde", "Auf einen Blick"); Zeile "Mailversand nodemailer" bleibt "offen" (Rest nur mit Hauptversion 10), Zeile "Übrige Bausteine" aufgeteilt in "behoben" und eine neue "offen"-Zeile fuer sharp und deepmerge-ts.
## Hinweise fuer Task 5 und 6
- Stack laeuft aus Task 4 gebaut (api, web, db, mailhog healthy); Plattenstand 80 % nach `docker image prune -f` (vorher kurzzeitig 84 %).
- **Protokoll-Zeilen noch zu schliessen:** "Entwicklungswerkzeuge im Server-Abbild", "Paketverwalter npm im Node-Grundabbild" (Task 5), "Laenge der Echtheitspruefung bei AES-GCM" (Task 5); Task 6 muss "Stand nach der Behebung" mit den Abschlusszahlen, "Auf einen Blick" (Bullet "Was noch zu tun ist" nennt bereits Abbilder + AES-GCM) und Verlauf nachziehen. Offen bleiben: xlsx, node-forge, glib, nodemailer (Hauptversion 10), sharp, deepmerge-ts, Gesundheitspruefung web, Kopfzeilen/HTTPS.
- Die Abbild-Trivy-Zahlen im Protokoll ("Erste vollstaendige Pruefung") sind noch die Rohwerte; Task 6 misst die neuen.
- Der Rest der api-Abhaengigkeiten ist unveraendert; die alten Trivy-Abbild-Zaehlungen (api C6 H116) sinken durch Task 4 bereits deutlich -- erst die Abschlussmessung zeigt es.
- **CLAUDE.md-Zeilen (D-26), die jetzt hinterherhinken (nicht angefasst):** Next.js 15.5.19 -> 15.5.27; NestJS 11.1.27 -> 11.2.7; Vitest (apps/web) 4.1.9 -> 4.1.11. Express 5.2.1 und Prisma 6.19.3 unveraendert. Der Eintrag "Prisma: pin exactly" stimmt weiter (Lockfile 6.19.3; `package.json` hat `^6.0.0`, das war schon vorher so).
- Im Desktop-Lockfile hat sich `Cargo.lock` geaendert: der naechste CI-Push auf main baut die Desktop-Pakete neu (erwartet, D-24).
- Die Dev-Bausteine tinypool/vitest 3 (API-Tests) sind nur mit einem vitest-4-Wechsel der API zu beheben (neue Hauptversion der Testwerkzeuge, nicht Teil dieses Auftrags); im Protokoll ist das als Verlaufsnotiz genannt, aber nicht als eigene Tabellenzeile.
- Orchestrator-Frage fuer spaeter: nodemailer 10 ist die groesste uebrige Produktionsposition (2 hoch, 3 mittel); ein Wechsel waere ein eigener kleiner Auftrag (Nutzung beschraenkt auf `createTransport`/`sendMail`).
## Task 5: AES-GCM-Tag, Laufzeitabbilder ohne Entwicklungswerkzeuge, Schutz-Kopfzeilen (erledigt)
**Commits (nicht gepusht):**
- `072f955` fix(quick-261009-p0m): AES-GCM-Entschluesselung verlangt 16-Byte-Tag (Teil A, Tests zuerst rot)
- `2a8a7d4` build(quick-261009-p0m): Laufzeitabbilder ohne Entwicklungswerkzeuge (Teil B + Doku)
- `fceec19` fix(quick-261009-p0m): Schutz-Kopfzeilen in der Weboberflaeche, X-Powered-By abgeschaltet (Zusatzauftrag des Orchestrators)
Nicht eingecheckt (Vorgabe): `checks/task5-image.txt`, `checks/fresh-db-start.sh`, diese Datei.
### Teil A (AES-GCM)
Tests zuerst: 5 neue Faelle in `crypto.service.spec.ts` (4 Byte, 17 Byte, leer, gekipptes Bit, Rundlauf inkl. leerem Text und Umlauten). Vor der Aenderung **3 rot** (4 Byte, 17 Byte, leer); echt unsicher war nur der 4-Byte-Fall (Node akzeptierte ihn und entschluesselte), 17 Byte und leer warf Node schon, aber mit anderer Meldung als die neue `16 bytes`-Pruefung (daher rot); das gekippte Bit wurde schon vorher abgelehnt. Danach 13/13 gruen. `decrypt` prueft `authTag.length === 16` vor dem Decipher und uebergibt `authTagLength: 16` (auch bei `createCipheriv`, Ausgabe unveraendert). `biome format --write` + `biome check --write` (sortierte die Imports und brach alte Zeilen im Spec auf 100 Zeichen um, das Format der Projektkonfiguration; daher groesserer Diff im Spec als nur die neuen Tests).
### Teil B (Abbilder) -- umgesetzt, kein Rueckbau noetig
- `apps/api/Dockerfile`: neue Stufe `prod-deps` (kopiert Manifeste, Lockfile, Workspace-Datei und `apps/api/prisma/`, dann `pnpm install --frozen-lockfile --prod --filter=@tessera/api...`; der `postinstall` von apps/api erzeugt den Prisma-Client dort, weil `prisma` in `dependencies` liegt). Laufzeitstufe `FROM node:24-alpine`, entfernt als Erstes `/usr/local/lib/node_modules/{npm,corepack}`, `/opt/yarn-v1.22.22`, `/usr/local/bin/{npm,npx,corepack,yarn,yarnpkg}`; `node_modules` kommen aus `prod-deps`; die lange `.prisma`-COPY-Zeile mit festem Pfad entfaellt. `apps/web/Dockerfile`: nur dieselbe Entfernung in der Laufzeitstufe.
- **Groessen und Fundzahlen** (`checks/task5-image.txt`, Trivy 0.75.0 `--scanners vuln`, `--image-src docker`, Betriebssystem + Node-Pakete):
| Abbild | Groesse vorher | nachher | critical | high | medium | low |
|---|---|---|---|---|---|---|
| api vorher (nach Task 4) | 1,59 GB (1592651888 B) | | 3 | 45 | 41 | 2 |
| api nachher | | 1,12 GB | **0** | **6** | 4 | 0 |
| web vorher (nach Task 4) | 359 MB | | 0 | 11 | 13 | 1 |
| web nachher | | 359 MB | 0 | **3** | 1 | 0 |
Die Web-Groesse bleibt gleich: `rm` in einer spaeteren Schicht verkleinert das Abbild nicht (nur die Sicht von Trivy); das ist im Protokoll so erklaert und die Doku verspricht "kleiner" nur fuer das Server-Abbild. Die Rohwerte der ersten Messung im Protokoll (api C6 H116, web C2 H19) bleiben dort als "Erste vollstaendige Pruefung"; Task 6 misst neu.
- **Gates (Sentinel `task5 ok`, Kette aus dem Plan wortgetreu, Log `scratchpad/task5-gate.log`):** Crypto-Tests, biome, `authTagLength` vorhanden, `tsc --noEmit`, komplette API-Tests, `docker compose up -d --build --wait api web`, `/login` = 200, `@prisma/client` loest aus `/app/apps/api` auf, `node` im web-Container, **keine** Paketverwaltung in api und web, keine Entwicklungswerkzeuge in `/app/node_modules/.pnpm` (vitest, tinypool, turbo, biome, Nest-CLI, schematics: alle weg), `@prisma+client@` vorhanden, **`fresh-db-start.sh` -> `migrationen=63`, `frische-datenbank ok`**, Fundzahl nachher < vorher, "keine Paketverwaltung" in der Betriebsanleitung, e2e-cert all, e2e-changelog, nc-test-setup, e2e-files, e2e-transfer, mail-smoke (`mail ok`), "Zugangsdaten"-Bullet, Link-Pruefer (12 Links, 0 kaputt). Zusaetzlich vorab ein eigener Lauf der e2e-Kette (`E2E-ALL-OK`).
- apk (Paketverwaltung des Betriebssystems) bleibt im Abbild; die Vorgabe nannte nur npm, npx, corepack und yarn.
- Rueckbau/Fallback: **nicht benutzt**. Keine devDependency wird zur Laufzeit importiert (Start gegen frische DB und alle Rauchtests gruen).
### Zusatzauftrag: Schutz-Kopfzeilen (eigener Commit `fceec19`)
- `apps/web/next.config.ts`: `poweredByHeader: false` und `headers()` fuer `/:path*`: `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, `X-Frame-Options: SAMEORIGIN`, `Content-Security-Policy: frame-ancestors 'self'` (nur diese Richtlinie), `Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()`, `Cross-Origin-Opener-Policy: same-origin-allow-popups`. Keine COEP/CORP (koennten eingebettete Bilder/Kacheln sperren, nichts zu schuetzen).
- Vorpruefung im Code: Zwischenablage nur `clipboard.writeText` (clipboard-write bleibt erlaubt, per `featurePolicy` im Browser bestaetigt: clipboard-write und fullscreen true, camera und geolocation false); Benachrichtigungen haben keine Permissions-Policy-Direktive; keine Nutzung von getUserMedia/Geolocation/Zahlung/USB. Alle `window.open`/`target=_blank` tragen `noopener`; **kein** `window.opener`, `postMessage` oder `BroadcastChannel` im Web-Quelltext -> COOP sicher. Nextcloud Login Flow v2 ist ein einfacher `<a target=_blank rel=noopener>` (Abfrage laeuft serverseitig). Desktop: Tauri navigiert das Hauptfenster direkt auf die Server-Adresse (`window.navigate`), kein Rahmen. XFrame-Kacheln/eigene Module setzen `sandbox` und `allow=""` und rahmen fremde Seiten ein -- die Kopfzeilen von Tessera betreffen nur das Einrahmen von Tessera.
- API: Express sendete `X-Powered-By: Express` (vorher per `curl -I` bestaetigt). `configureHttp` ruft jetzt `app.disable('x-powered-by')` (Typ `HttpApp` auf `NestExpressApplication`, `main.ts` erzeugt `NestFactory.create<NestExpressApplication>`); Test in `http-setup.spec.ts` (der Test "Anfragelog und Cookies kommen zuerst" ignoriert die `disable`-Aufrufe).
- Tests: neu `apps/web/src/next-config.test.ts` (4 Faelle; eine Konfigurationsdatei-Test gab es vorher nicht). Web-Suite gesamt 161 Dateien / 1949 Tests, `pnpm lint`, `pnpm type-check` gruen.
- **Pruefung am neu gebauten lokalen Stack:** `curl -I /login`, `/` (307) und 404-Seiten tragen alle Kopfzeilen, **kein** `X-Powered-By` bei Web und API (auch nach dem Neubau auf HEAD erneut geprueft). Der Pfad `/api-proxy/...` (Umleitung zur API) traegt die Web-Kopfzeilen nicht, nur die Antwort der API; sie sind fuer JSON-Antworten nicht noetig, ZAP hat sie dort nicht beanstandet (nur auf `/login`, `/reset-password`).
- **Browser (Playwright-Kern, headless Chromium, Dunkelmodus, `scratchpad/smoke/*.cjs`):** Anmeldung und Dashboard, 0 Konsolenmeldungen (kein Hinweis auf unbekannte oder verletzte Richtlinien); Zertifikatsmanager laedt, Testzertifikat + Schluessel hochgeladen, "Fullchain herunterladen" liefert `p0m-smoke.test-fullchain.pem`; Dateien: "Im Browser anmelden" -> Link oeffnet neuen Tab auf `.../login/v2/flow`, `window.opener === null`; eigenes Modul (https://example.com/) im Rahmen geladen (Modul danach geloescht, API 200). Der MCP-Browser stand dem Agenten nicht zur Verfuegung, daher das Playwright-Paket aus dem npx-Cache.
- Protokoll: Zeilen "Fehlender Schutz gegen Einrahmen", "Weitere fehlende Schutz-Kopfzeilen", "X-Powered-By" jetzt "behoben in der Anwendung, beim oeffentlichen Lauf erneut pruefen"; neue Zeile "Vollstaendige Inhaltsrichtlinie (Content Security Policy)" = **offen** mit Grund; "Auf einen Blick" (Bullet Pruefung von aussen) und Verlaufseintrag "Schutz-Kopfzeilen in der Anwendung". CHANGELOG: ein "Sicherheit:"-Bullet unter "Geaendert". Entwicklungsanleitung: Absatz "Schutz-Kopfzeilen der Weboberflaeche" unter "Konventionen und Fallstricke" (inkl. Warnung: clipboard-write nie sperren; `noopener` beibehalten).
### Abweichungen (Task 5)
1. **[Rule 3] Dokumentation getrennt committet:** Die Doku von Teil B und die des Zusatzauftrags liegen in denselben Dateien; fuer zwei saubere Commits wurden die Zusatz-Hunks fuer den ersten Commit voruebergehend herausgenommen und danach wiederhergestellt (kein Inhaltsverlust, beide Commits einzeln pruefbar).
2. Teil A: `biome check --write` sortierte die Imports in `crypto.service.ts` um (Biome-Regel `organizeImports`), und `biome format` brach alte Zeilen im Spec um. Verhalten unveraendert.
3. Die CHANGELOG-Aenderung kam nach dem ersten Bau; deshalb wurde der Stack nach dem letzten Commit nochmals mit `--build` neu gebaut (Voraussetzung von Task 6).
### Hinweise fuer Task 6
- Stack laeuft gebaut aus HEAD `fceec19` (`docker compose up -d --build --wait api web` nach dem letzten Commit, alle healthy). Lokale Abbilder: `tessera-ctl-api:latest` 1,12 GB, `tessera-ctl-web:latest` 359 MB; fuer den Abschlusslauf `SCAN_IMAGES="tessera-ctl-api:latest tessera-ctl-web:latest"` verwenden. Erwartung der Skriptzaehlung (OS + Node, nicht `--scanners vuln`-identisch, kann abweichen): api ~C0 H6, web ~H3.
- **Protokoll noch zu schliessen in Task 6:** "Stand nach der Behebung" mit Abschlusszahlen; die Tabelle "Erste vollstaendige Pruefung" behaelt die Rohwerte. Alle Zeilen "Einordnung der Befunde" haben jetzt einen Status; "offen" bleiben xlsx, node-forge, glib, nodemailer 10, sharp/deepmerge-ts, Gesundheitspruefung web, Vollstaendige Inhaltsrichtlinie, HTTPS/Proxy, Pnpm-Arbeitsbereichseinstellungen u. a.; die drei neuen Statuswoerter "behoben in der Anwendung, beim oeffentlichen Lauf erneut pruefen" enthalten das Wort "behoben" und passen auf die Task-6-Zeilenpruefung (Status + letzte Spalte nicht leer). Task-6-Gate: `grep -qx "semgrep-regel gcm-no-tag-length=0"`: die Regel sollte jetzt nichts mehr finden.
- Der Beweisdateiname `checks/task5-image.txt` enthaelt die Zeile `ergebnis umgesetzt`.
- Platte: 55 % belegt (78 GB frei) nach den Arbeiten; `docker image prune -f` hat nichts mehr zu entfernen.
- CLAUDE.md-Tabelle (D-26) unveraendert; zusaetzlich hinkt jetzt nichts Neues nach.
- Orchestrator-Hinweis: der naechste ZAP-Lauf (oeffentliche Adresse, nach dem naechsten alpha-Pull) sollte die Zeilen der Kopfzeilen auf "behoben" setzen oder pruefen, ob der Proxy sie doppelt sendet; die Anwendung sendet sie jetzt selbst.
## Task 6: Abschlusslauf, Protokoll-Abschluss, CLAUDE.md, Aufraeumen (erledigt)
**Commits (nicht gepusht):**
- `bc7fffe` docs(quick-261009-p0m): Technology-Stack-Zeilen auf den Stand der Lockdatei gebracht (CLAUDE.md, Zusatzauftrag, ueberschreibt D-26)
- `b085889` docs(quick-261009-p0m): Sicherheitsprotokoll mit Stand nach der Behebung
Nicht eingecheckt (Vorgabe): `checks/final-run.txt`, `baseline/SUMMARY.txt` (Block "nach Behebung"), diese Datei. Gate-Sentinel `task6 ok` gedruckt (Kette aus dem Plan wortgetreu).
### Abschlusslauf (`checks/final-run.txt`)
Frischer Klon (`git clone --no-local`) von HEAD `fceec19`, Container `gitea/runner-images:ubuntu-latest` (Netz `gitea`, Docker-Socket, frisches Werkzeug-Volume, `GITHUB_REF=refs/heads/main`, kein Kanarienvogel), Skript `all`: **rc=0**.
```
gitleaks findings=0
pnpm-audit-prod critical=0 high=9 moderate=3 low=0
osv-scanner packages=20
semgrep findings=33 errors=4
trivy-fs critical=0 high=9 medium=4 low=0 misconfig=2 secrets=0
trivy-image-api critical=0 high=6 medium=4 low=0
trivy-image-web critical=0 high=3 medium=1 low=0
semgrep-regel gcm-no-tag-length=0
```
- Die Zeilen der Abbilder heissen `trivy-image-api`/`-web`, weil das Skript den Namen aus dem Abbildnamen ableitet. Mit den lokalen Compose-Namen (`tessera-ctl-api:latest`) hiessen sie `trivy-image-tessera-ctl-api` (erster Versuch, gleiche Zahlen) und haetten das Plan-Gate nicht getroffen. Loesung ohne Skriptaenderung: der gesamte Lauf wurde wiederholt mit den lokalen Abbildern kurz als `api:p0m-final`/`web:p0m-final` getaggt (kein Registry-Name; Tags danach entfernt). Die Zeilen der Datei stammen aus diesem zweiten Lauf; er war der erste Lauf mit wiederverwendetem Werkzeug-Volume (`semgrep ... bereit (vorhanden)`), also ein lokaler Beleg fuer Annahme A2.
- Reste der Abbild-Funde (per Trivy am lokalen Abbild nachgemessen): api = deepmerge-ts, node-forge, nodemailer, xlsx (je HIGH, alle ohne Korrektur in der Versionslinie); web = nur sharp.
- osv 20 = 8 npm (xlsx, node-forge, nodemailer, sharp, deepmerge-ts, vitest 3.2.6, @vitest/mocker 3.2.6, tinypool) + 12 crates (glib, anyhow, event-listener, h2, quick-xml x2, proc-macro-error, unic-* x5).
- Semgrep Rest: 14 mutable Aktions-Tags (+1 curl|sh), 9 non-literal-regexp, 4 TLS-Umgehungen (bewusst), 1 open-redirect, 1 prototype-pollution-loop, 3 pnpm-Haertungsschluessel; die GCM-Regel ist weg.
### Protokoll-Abschluss
- Neuer Abschnitt `## Stand nach der Behebung` (Vorher/Nachher-Tabelle je Werkzeug, Liste des Offenen in Alltagssprache) und Verlaufseintrag `### 2026-10-09 — Stand nach der Behebung` (Kurzfassung mit Verweis; Tabelle steht aus Wartungsgruenden nur im Abschnitt). "Auf einen Blick" (Bausteine, automatische Pruefung, Was noch zu tun ist) auf den Endstand gebracht.
- "Einordnung der Befunde": 33 Zeilen, alle mit Stand und Begruendung. Neu: Zeile "Weitere Hinweise zu Bausteinen der Desktop-App" (11 Rust-Bausteine, offen; Patch-Fassungen fuer anyhow 1.0.103, event-listener 5.4.2, h2 0.4.16 existieren) und Zeile "Testwerkzeuge vitest 3 / tinypool" (offen). Pipeline-Zeile auf 14 mutable Tags aktualisiert, GCM-Zeile um "Semgrep meldet nicht mehr" ergaenzt.
- Wortregeln geprueft: kein "Lizenz", "Mandant", keine Du-Form, keine IP-Adressen; Link-Pruefer 12 Links 0 kaputt; gitleaks auf Protokoll und volle Historie (1377 Commits) 0 Funde.
### CLAUDE.md (Zusatzauftrag, ueberschreibt D-26)
Zeilen angepasst nach `pnpm-lock.yaml` (`importers:`): Next.js 15.5.27, NestJS 11.2.7, Vitest (apps/web) 4.1.11, dazu der Satz unter "Recommended But Not Adopted" (Next 15.5.27). Express 5.2.1, Prisma 6.19.3, React 19.2.7, Tailwind 4.3.1, TypeScript 5.9.3, Biome 2.5.0, Turborepo 2.9.18, Vitest (apps/api) 3.2.6, next-intl, Zustand und die uebrigen Zeilen stimmen unveraendert. Kopfhinweis ergaenzt: Stand 2026-09-09; die drei Zeilen am 2026-10-09 gegen die Lockdatei neu geprueft (Docker-Zeilen bleiben bewusst mit ihrem Messdatum). nodemailer und undici stehen nicht in der Tabelle.
### Aufraeumen
Entfernt (Name fuer Name): `semgrep/semgrep:1.180.0`, `aquasec/trivy:0.75.0`, `ghcr.io/aquasecurity/trivy:0.75.0`, `ghcr.io/google/osv-scanner:v2.6.0`, `ghcr.io/gitleaks/gitleaks:latest` und `:v8.30.1`, `curlimages/curl:latest`; `docker image prune -f` hatte danach nichts zu entfernen (nie `-a`). Behalten: ZAP-Abbild (Digest geprueft), `gitea/runner-images:ubuntu-latest`. Wegwerf-Klon, Werkzeug-Volume `p0m-final-tools` und die Zwischentags geloescht, root-eigene Berichte per Container entfernt; in `security-reports/` bleibt nur der ZAP-Ordner von alpha. **Platte: 54 % belegt (80 GB frei)**, weit unter 85 %.
### Abweichungen (Task 6)
1. **Abbildnamen im Abschlusslauf:** siehe oben, Lauf mit `api:p0m-final`/`web:p0m-final` statt `tessera-ctl-*:latest` (Plan-Gate verlangt `trivy-image-api`/`-web`; kein Skripteingriff noetig).
2. **Zusatz:** CLAUDE.md-Aktualisierung (Orchestrator-Auftrag) als eigener Commit `bc7fffe`.
3. **Protokoll:** Die Vorher/Nachher-Tabelle steht im Abschnitt "Stand nach der Behebung"; der Verlaufseintrag mit dem vom Plan verlangten Titel ist eine Kurzfassung mit Verweis (kein doppelt gepflegtes Zahlenwerk).
4. Zwei Zeilen in "Einordnung der Befunde" ergaenzt (Desktop-Hinweise, Testwerkzeuge): sie fehlten bisher, obwohl osv-scanner sie zaehlt (Plan: jede Zahl braucht Stand und Grund).
## Gesamtergebnis: vorher / nachher
| Werkzeug | Grundlinie (roh) | nach Ausnahmelisten (Task 1) | nach Behebung (Task 6) |
|---|---|---|---|
| gitleaks (Historie) | 20 (0 echt) | 0 | 0 |
| pnpm audit --prod | C5 H73 M68 L5 (151) | unveraendert | **C0 H9 M3 L0 (12)** |
| pnpm audit (alle) | C7 H85 M74 L5 (171) | -- | C2 H9 M5 L0 (16) (Task 4) |
| osv-scanner | 43 (nur pnpm-lock) | 56 (mit Cargo.lock) | 20 |
| Semgrep | 58 / 59 Scanfehler | 34 (ERROR 2) / 4 | 33 (ERROR 1) / 4; GCM-Regel 0 |
| Trivy fs | C5 H73 M68 L5, secrets 7, misconfig 2 | C5 H73 M70 L5, secrets 0 | C0 H9 M4 L0, secrets 0, misconfig 2 |
| Abbild api | C6 H116 M98 L7, 1,66 GB | C6 H116 M99 L7 | **C0 H6 M4 L0, 1,12 GB** |
| Abbild web | C2 H19 M21 L1, 359 MB | C2 H19 M22 L1 | **C0 H3 M1 L0, 359 MB** |
| ZAP alpha (direkt, ohne Proxy) | -- | -- | 0 / 2 / 6 / 3 (Task 3, vor den Kopfzeilen; nicht neu gemessen) |
## Offene Punkte (alle im Protokoll mit Grund)
1. **xlsx 0.18.5, node-forge 1.4.0:** keine bereinigte Fassung (Ersatz von xlsx waere ein eigener Auftrag).
2. **nodemailer (2 H, 3 M), sharp (3 H), deepmerge-ts (1 H):** Korrektur nur mit neuer Haupt-/Null-Minor-Version (nodemailer 10 ist die groesste Restposition).
3. **vitest/@vitest/mocker 3.2.6, tinypool 1.1.1 (API-Tests):** nur Entwicklung, Korrektur braucht vitest 4 in apps/api (nicht im Abbild).
4. **Desktop:** glib 0.18.5 plus 11 weitere Rust-Hinweise; **anyhow 1.0.103, event-listener 5.4.2, h2 0.4.16 sind Patch-Updates in der Versionslinie** (`cargo update -p ... --precise`) und koennten als kleiner Folgeauftrag eingespielt werden (waere ein Cargo.lock-Commit).
5. **Web-Abbild ohne Gesundheitspruefung** (weder Dockerfile noch Compose).
6. **Vollstaendige Inhaltsrichtlinie (CSP)**: bewusst nicht gesetzt, eigener Auftrag.
7. **Pipeline-Haertung:** 14 bewegliche Aktions-Tags, ein curl|sh, drei pnpm-Arbeitsbereichsschluessel (niedrig).
8. **Ausstehender oeffentlicher ZAP-Lauf** (siehe unten).
9. Nicht gebaut: woechentlicher geplanter Lauf der Pruefung (Forschung: jetzt nicht noetig); Anheben der Aktions-Tags.
## Ausstehender oeffentlicher ZAP-Lauf
Der erste Lauf ging direkt gegen die Anwendung auf alpha, weil die oeffentliche Adresse vom Dev-Host nicht erreichbar war (Zeitueberschreitung Port 443). Nach dem naechsten alpha-Pull (mit den Kopfzeilen aus `fceec19`): `sh .gitea/scripts/zap-baseline.sh` (Standardziel = oeffentliche Adresse; falls Basic Auth 401 liefert: `ZAP_BASIC_AUTH_FILE` mit `benutzer:passwort` ausserhalb des Repos). Danach im Protokoll: neue Zeile in "Pruefung von aussen gegen alpha", Verlaufseintrag, und die drei Zeilen "behoben in der Anwendung, beim oeffentlichen Lauf erneut pruefen" sowie HTTPS/Proxy auf ihren Endstand setzen.
## Checkliste erster CI-Lauf nach dem Push (Nutzer/Orchestrator)
- [ ] Job `security` erscheint **nach `publish`** (nur main und Tags `v*`), publish und Freigaben unveraendert.
- [ ] Jobfarbe: gelb/gruen trotz Funden (Annahme A3: `continue-on-error` auf Job-Ebene), kein anderer Job haengt daran.
- [ ] Im Lauf-Protokoll stehen alle `SECURITY-SUMMARY`-Zeilen (gitleaks, pnpm-audit-prod, osv-scanner, semgrep, trivy-fs, trivy-image-api, trivy-image-web, `fertig`); Zahlen etwa wie oben (Abbild-Zahlen mit Registry-Abbildern von publish).
- [ ] Artefakt `sicherheitsberichte` herunterladbar? (Annahme A4, `upload-artifact@v3`; best effort, Ausfall ist kein Fehler.)
- [ ] Zweiter Lauf: Werkzeuge aus dem `act-toolcache`-Volume wiederverwendet (Zeile "bereit (vorhanden)"; Annahme A2; lokal mit Volume bestaetigt).
- [ ] Dauer des Jobs (Limit 30 min) und Plattenstand des Runners.
- [ ] Der Desktop-Job baut die Pakete neu, weil `Cargo.lock` sich geaendert hat (erwartet).
## Hinweise an den Orchestrator
- CHANGELOG: "Sicherheit:"-Bullets liegen innerhalb der bestehenden Gruppen Neu/Geaendert/Behoben (D-21: die "Was ist neu"-Anzeige liest nur diese drei).
- Entfernte Pakete: `@nestjs-modules/mailer`, `ews-javascript-api` (D-22); keine neuen Paketnamen, 232 entfallen.
- Overrides (15) und Gruende stehen in Task 4; Prisma 6.19.3, Next 15, NestJS 11 unveraendert.
- Keine Modulversion geaendert, nichts gepusht, nichts deployed.
- CLAUDE.md-Tabelle ist jetzt aktuell (siehe oben).
## Self-Check: PASSED
Gefunden: `docs/sicherheitsprotokoll.md`, `checks/final-run.txt`, `baseline/SUMMARY.txt` (Block "nach Behebung"), Commits `bc7fffe` und `b085889` sind Vorfahren von HEAD, `git status` ausserhalb von `.planning/` sauber; Gate-Sentinel `task6 ok`.
## Review fixes
Review findings WR-01, WR-02, WR-04, WR-05 and IN-01 to IN-04 fixed; commits `bf632d9` (scripts, Dockerfiles, next.config comment) and `8a1218a` (docs). Not pushed. Details per finding: "Fix status" in `261009-p0m-REVIEW.md`.
- **Image start proof** is now versioned: `bash .gitea/scripts/image-start-check.sh [Abbild]` (replaces `checks/fresh-db-start.sh`, which stays here as history). Rebuilt stack (`docker compose up -d --build api web`): api healthy; proof printed `migrationen=63`, `frische-datenbank ok`.
- **Build fails when it must:** broken `schema.prisma` in a throwaway Dockerfile fails at the explicit `prisma generate` step (rc 1); a runner stage that keeps npm fails at the absence check. Both throwaway images removed; real Dockerfiles unchanged by the experiment.
- **security-scan.sh** in `gitea/runner-images:ubuntu-latest` (fresh throwaway clone, socket, fresh tool volume, `GITHUB_REF=refs/heads/main`, both local images): rc 0, 63 s, per-tool lines (gitleaks 0, pnpm-audit-prod critical 0 high 9, osv 20, semgrep 33/4, trivy-fs secrets 0, images api C0 H6, web C0 H3). Also: shallow clone -> `unvollstaendig`; forced limits -> `zeitgrenze`, forced tiny budget -> `gesamtbudget`; offline run rc 0 with 12 skipped lines (Task 1 gate needs >= 7); no semgrep container left behind.
- **ZAP:** local passive proof (POST=0, header only to the target, never to a second host or a prefix-port lookalike); special-character password works; handover path for uid != 1000 exercised.
- **Gates:** `ci.yml` node check from PLAN Task 1 prints `ci.yml ok` (file unchanged); `sh -n` on all `.gitea/scripts/*.sh`, `bash -n` on the new script, shellcheck (via container image, removed afterwards) shows only SC2329 info for trap handlers; link-check 12 links, 0 broken; protocol table gate 34 rows ok; gitleaks `dir` on the changed docs/scripts clean.
- **Environment notes:** semgrep image (1.5 GB) now stays cached on the dev host and will on the runner host after the first CI run (needed for the digest-pinned scan; documented in `docs/ci-cd-setup.md`). Disk 57 % after cleanup (`docker image prune -f` only). No shellcheck installed on the host; it was run from `koalaman/shellcheck:stable`.
- **Docs updated:** `docs/sicherheitsprotokoll.md` (Verlauf entry "Codeprüfung des Sicherheitsauftrags", table row, sums 9 reviews / 98 findings / 84 fixed, new open row for WR-03), `docs/ci-cd-setup.md`, `docs/anleitung-entwicklung.md`, `docs/anleitung-betrieb.md`.
### Checklist after the first real CI run (WR-03)
- [ ] Job `security` ends green or neutral, not red (red would mean the Gitea version ignores job-level `continue-on-error`).
- [ ] `timeout-minutes: 30` is honoured; first run duration (semgrep image pull of about 1.5 GB and Trivy DB included) stays below 25 minutes; the `SECURITY-SUMMARY dauer=...` line shows it.
- [ ] The next push's `quality`/`test` is not noticeably delayed by the single runner; if it is, move the scan to a nightly schedule plus tags `v*`.
- [ ] `SECURITY-SUMMARY semgrep findings=... errors=...` appears (container path works inside the real job container with the host socket) and artifact `sicherheitsberichte` is best effort.