diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c4fbe3..3bdd382 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,19 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). ### Sicherheit +## [0.15.0] – 2026-09-18 + +### Hinzugefügt +- `docs/sicherheit.md`: Zuordnung aller Maßnahmen zu den OWASP Top 10, samt der Punkte, die bewusst offen bleiben (#024) +- Content-Security-Policy mit frischem Nonce je Anfrage (#024) +- Sitzungen lassen sich widerrufen: gesperrtes Konto oder neues Passwort beendet laufende Sitzungen sofort (#024) +- Benutzerverwaltung auf der Kommandozeile: `npm run benutzer -- liste|sperren|entsperren|passwort` (#024) + +### Behoben +- Server Actions verließen sich allein auf den Schutz in `proxy.ts`; jede der 27 Aktionen prüft die Sitzung jetzt selbst (#024) +- Die Anmeldung prüfte die Herkunft des Formulars nicht – damit war Login-CSRF möglich (#024) +- `npm audit` meldete vier hohe Schwachstellen in Beiwerk von Prisma (`mysql2`, `deepmerge-ts`); über `overrides` angehoben, jetzt ohne Befund (#024) + ## [0.14.1] – 2026-09-18 ### Behoben diff --git a/DEVLOG.md b/DEVLOG.md index 101fa08..0430020 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -2,7 +2,7 @@ ## Zeitübersicht -**Gesamt:** 9 h 43 min +**Gesamt:** 10 h 28 min | Issue | Zeit | |---|---| @@ -29,6 +29,7 @@ | #018 Feldtest auf echten Hosts | 0 h 40 min | | #022 Cluster-Abgleich | 0 h 25 min | | #023 Feldtest PBS | 0 h 35 min | +| #024 OWASP-Härtung | 0 h 45 min | | Meilenstein | Zeit | |---|---| @@ -51,7 +52,7 @@ ## 2026-09-17 · Sitzung 1 · 19:36–laufend **Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0) -**Version:** 0.14.1 +**Version:** 0.15.0 ### Erledigt - Auftrag gelesen, Rückfragen gestellt @@ -138,6 +139,9 @@ - Der erfundene PBS-Testfall hatte den Fehler verdeckt. Lehre: ausgedachte Testdaten bestätigen nur die eigene Annahme - Uninstall zum ersten Mal auf einem echten Host geprüft – restlos sauber, damit ist #018 vollständig +- Durchsicht nach OWASP (#024): drei echte Lücken gefunden – Server Actions ohne eigene Zugriffsprüfung, Login-CSRF und fehlende CSP. Alle behoben und mit Tests abgesichert +- Erkenntnis: Der Schutz lag vollständig in einer einzigen Schicht (`proxy.ts`). Das fällt erst auf, wenn man die Endpunkte einzeln betrachtet statt die Oberfläche + ### Nächster Schritt - #016 Benutzerhandbuch mit Screenshots, danach #017 Favicons und #019 CI-Pipeline - Für 1.0.0 fehlt noch: PMG (kein Host verfügbar) und der Fall „aktive Subscription auf PBS“ (#023) diff --git a/docs/anleitung/bilder/audit.png b/docs/anleitung/bilder/audit.png index ba4adca..206ebae 100644 Binary files a/docs/anleitung/bilder/audit.png and b/docs/anleitung/bilder/audit.png differ diff --git a/docs/anleitung/bilder/benachrichtigungen.png b/docs/anleitung/bilder/benachrichtigungen.png index cca8956..b020bd6 100644 Binary files a/docs/anleitung/bilder/benachrichtigungen.png and b/docs/anleitung/bilder/benachrichtigungen.png differ diff --git a/docs/anleitung/bilder/dashboard.png b/docs/anleitung/bilder/dashboard.png index ab0dc01..214201f 100644 Binary files a/docs/anleitung/bilder/dashboard.png and b/docs/anleitung/bilder/dashboard.png differ diff --git a/docs/anleitung/bilder/host-detail.png b/docs/anleitung/bilder/host-detail.png index c21d7a2..000bf3a 100644 Binary files a/docs/anleitung/bilder/host-detail.png and b/docs/anleitung/bilder/host-detail.png differ diff --git a/docs/anleitung/bilder/hosts.png b/docs/anleitung/bilder/hosts.png index 6891860..866afd8 100644 Binary files a/docs/anleitung/bilder/hosts.png and b/docs/anleitung/bilder/hosts.png differ diff --git a/docs/anleitung/bilder/jahresbahn.png b/docs/anleitung/bilder/jahresbahn.png index 57a507d..3a43f82 100644 Binary files a/docs/anleitung/bilder/jahresbahn.png and b/docs/anleitung/bilder/jahresbahn.png differ diff --git a/docs/anleitung/bilder/kunden.png b/docs/anleitung/bilder/kunden.png index a162ee4..8574bbc 100644 Binary files a/docs/anleitung/bilder/kunden.png and b/docs/anleitung/bilder/kunden.png differ diff --git a/docs/anleitung/bilder/nicht-zugeordnet.png b/docs/anleitung/bilder/nicht-zugeordnet.png index 803b8ef..21c54f8 100644 Binary files a/docs/anleitung/bilder/nicht-zugeordnet.png and b/docs/anleitung/bilder/nicht-zugeordnet.png differ diff --git a/docs/anleitung/bilder/rechnungen.png b/docs/anleitung/bilder/rechnungen.png index 5129ec1..ee6ebef 100644 Binary files a/docs/anleitung/bilder/rechnungen.png and b/docs/anleitung/bilder/rechnungen.png differ diff --git a/docs/anleitung/bilder/tokens.png b/docs/anleitung/bilder/tokens.png index f390292..1599c07 100644 Binary files a/docs/anleitung/bilder/tokens.png and b/docs/anleitung/bilder/tokens.png differ diff --git a/docs/anleitung/bilder/warnregeln.png b/docs/anleitung/bilder/warnregeln.png index 69ed67e..5314533 100644 Binary files a/docs/anleitung/bilder/warnregeln.png and b/docs/anleitung/bilder/warnregeln.png differ diff --git a/docs/anleitung/bilder/warnungen.png b/docs/anleitung/bilder/warnungen.png index 74c86ba..18520c4 100644 Binary files a/docs/anleitung/bilder/warnungen.png and b/docs/anleitung/bilder/warnungen.png differ diff --git a/docs/issues/024-owasp-haertung.md b/docs/issues/024-owasp-haertung.md new file mode 100644 index 0000000..b235a19 --- /dev/null +++ b/docs/issues/024-owasp-haertung.md @@ -0,0 +1,34 @@ +# #024 Härtung nach OWASP +**Status:** erledigt +**Meilenstein:** v0.15.0 +**Labels:** security, backend +**Geschätzt:** 4 h **Tatsächlich:** 0 h 45 min + +## Ziel +Vor der Freigabe als fertiges Produkt eine Durchsicht gegen die OWASP Top 10 – und die gefundenen +Lücken schließen, statt sie zu vermerken. + +## Aufgaben +- [x] Zugriffskontrolle: jede Server Action prüft die Sitzung selbst, nicht nur der Proxy +- [x] Sitzungen widerrufbar machen (gesperrtes Konto, neues Passwort) +- [x] Login-CSRF: Herkunft der Anmeldung prüfen +- [x] Content-Security-Policy mit Nonce je Anfrage +- [x] Abhängigkeiten ohne bekannte Schwachstellen (`npm audit`) +- [x] Benutzerverwaltung auf der Kommandozeile (sperren, entsperren, Passwort) +- [x] `docs/sicherheit.md` mit der Zuordnung zu den OWASP Top 10 +- [x] Tests für Sitzungsurteil und Herkunftsprüfung + +## Gefunden und behoben +1. **Server Actions waren nur durch `proxy.ts` geschützt.** Keine einzige Aktion prüfte selbst. Der + Proxy greift zwar auch bei POST, aber eine einzige Schicht ist zu wenig für 27 schreibende Endpunkte. +2. **Login-CSRF möglich:** Der Anmelde-Endpunkt ist ein gewöhnliches Formular und prüfte die Herkunft + nicht. Eine fremde Seite hätte jemanden in ein untergeschobenes Konto anmelden können. +3. **Keine CSP.** Jetzt mit Nonce und `strict-dynamic`. +4. **Sitzungen ließen sich nicht beenden.** Ein gesperrtes Konto arbeitete bis zu zwölf Stunden weiter. +5. **`npm audit`: 4 hohe Meldungen** in `mysql2` und `deepmerge-ts` – beides Beiwerk von Prisma. Über + `overrides` angehoben statt Prisma zurückzustufen; danach keine Meldung mehr. + +## Bewusst nicht umgesetzt +- **Rollen:** Wer angemeldet ist, darf alles. Für ein internes Werkzeug vertretbar, in `docs/sicherheit.md` vermerkt. +- **Zwei-Faktor-Anmeldung:** Wer sie braucht, nutzt OIDC. +- Die Einschränkung der OIDC-Anmeldung (erlaubte Domänen, automatisches Anlegen) kommt mit #025. diff --git a/docs/issues/README.md b/docs/issues/README.md index 8ced41b..2f5c226 100644 --- a/docs/issues/README.md +++ b/docs/issues/README.md @@ -27,4 +27,5 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a | [#021](021-geheimnisse-verschluesseln.md) | Zugangsdaten verschlüsselt speichern | erledigt | v0.13.1 | 2 h | 0 h 20 min | | [#022](022-cluster-abgleich.md) | Cluster-Abgleich | erledigt | v0.14.0 | 3 h | 0 h 25 min | | [#023](023-feldtest-pbs-pmg.md) | Feldtest auf PBS und PMG | offen | v1.0.0 | 2 h | 0 h 35 min (PBS erledigt, PMG offen) | -| | **Summe** | | | **121 h** | **9 h 43 min** | +| [#024](024-owasp-haertung.md) | Härtung nach OWASP | erledigt | v0.15.0 | 4 h | 0 h 45 min | +| | **Summe** | | | **125 h** | **10 h 28 min** | diff --git a/docs/sicherheit.md b/docs/sicherheit.md new file mode 100644 index 0000000..4d93fdc --- /dev/null +++ b/docs/sicherheit.md @@ -0,0 +1,101 @@ +# Sicherheit + +Dieses Dokument hält fest, was ProxLedger gegen die üblichen Angriffe tut – und was es **nicht** tut. +Die Gliederung folgt den OWASP Top 10 (2021). + +## A01 – Fehlerhafte Zugriffskontrolle + +- `src/proxy.ts` schickt jede Anfrage ohne gültige Sitzung zur Anmeldung. Öffentlich sind nur die + Anmeldung, die Agent-API (eigene Tokens), die Gesundheitsprüfung, die beiden Agent-Skripte und die Symbole. +- **Jede Server Action prüft zusätzlich selbst** (`sitzungErforderlich()`). Server Actions sind eigene + Endpunkte; eine einzige Schutzschicht davor wäre zu wenig, falls der Musterausdruck im Proxy je + danebengreift. +- Die Prüfung geht in die Datenbank: Ein gesperrtes Konto oder ein Widerruf beendet laufende Sitzungen + sofort (`npm run benutzer -- sperren `), statt bis zum Ablauf des Tokens zu warten. +- Die Anmeldung prüft den `Origin`, damit keine fremde Seite jemanden unbemerkt in ein untergeschobenes + Konto anmeldet (Login-CSRF). Für Server Actions erledigt das Next.js selbst. + +**Nicht vorhanden:** Rollen. Wer angemeldet ist, darf alles. Für ein Werkzeug, das ein Systemhaus intern +betreibt, ist das vertretbar – für mehrere Mandanten wäre es das nicht. + +## A02 – Kryptografische Fehler + +- Passwörter: argon2id (`@node-rs/argon2`), keine eigene Krypto. +- Sitzungen: signiertes JWT (HS256) aus `APP_SECRET`, 12 Stunden gültig, Cookie `httpOnly`, `sameSite=lax` + und `secure`, sobald `APP_URL` auf `https://` steht. +- Zugangsdaten für SMTP und Webhooks liegen AES-256-GCM-verschlüsselt in der Datenbank + (`src/lib/geheimnisse.ts`); der Schlüssel wird aus `APP_SECRET` abgeleitet. +- Agent- und Enrollment-Tokens werden nur als SHA-256-Hash gespeichert. Das ist hier richtig und nicht + zu schwach: Die Tokens sind 32 Byte Zufall, es gibt nichts zu raten. + +**Wichtig:** `APP_SECRET` ist der Generalschlüssel. Geht er verloren, sind alle Sitzungen ungültig und die +gespeicherten Zugangsdaten unlesbar. Er gehört in die Sicherung. + +## A03 – Injektion + +- Datenbankzugriffe ausschließlich über Prisma mit gebundenen Werten; kein `$queryRawUnsafe` im Projekt. +- Ausgaben maskiert React selbst. +- Zusätzlich greift eine Content-Security-Policy mit frischem Nonce je Anfrage und `strict-dynamic` + (`src/proxy.ts`). Eingeschleustes `