This commit is contained in:
2026-03-25 22:03:39 +01:00
parent a0073b4fb1
commit faf4ca10c9
5603 changed files with 1030866 additions and 79 deletions

View File

@@ -0,0 +1,136 @@
# INCIDENT REPORT CREDENTIAL EXPOSURE
# Datum: 2026-02-22
# Klassifikation: SICHERHEITSVORFALL (intern)
---
## 1. Zusammenfassung
Im Rahmen der systematischen Sicherheitsanalyse (STEP 4.4 Klartext-Credentials
entfernen) wurde ein produktives E-Mail-Passwort im Klartext in einer
Konfigurationsdatei des Projekts entdeckt.
---
## 2. Betroffene Komponente
- **Datei:** `aza_email_config.json`
- **Modul:** AZA E-Mail (aza_email.py)
- **Feld:** `password` im `accounts`-Array
- **Betroffener Account:** Produktiver Mail-Account (Details bewusst nicht aufgeführt)
---
## 3. Entdeckung
- **Entdeckt durch:** Automatisierte Security Gap-Analyse (STEP 3) und
bestätigt bei der Implementierung von STEP 4.4
- **Entdeckungsdatum:** 2026-02-22
- **Entdeckungsmethode:** Grep-Suche nach `password`, `secret`, `token`
in allen JSON/YAML/Config-Dateien des Projekts
---
## 4. Risikoanalyse
### Worst-Case-Szenarien
| Szenario | Risiko | Schwere |
|----------|--------|---------|
| Unbefugter Zugriff auf das Dateisystem | Passwort-Exfiltration | HOCH |
| Account-Übernahme (Mail) | Lesen/Senden von E-Mails im Namen des Kontoinhabers | HOCH |
| Mail-Missbrauch | Spam, Phishing, Social Engineering über legitimen Account | HOCH |
| Laterale Bewegung | Falls Passwort wiederverwendet: Zugriff auf weitere Systeme | KRITISCH |
| Datenschutzverletzung | Zugriff auf medizinische Kommunikation (Patientendaten) | KRITISCH |
| Backup-/Export-Exposition | Passwort in Backups, Cloud-Syncs oder Transfers vorhanden | MITTEL |
### Besondere Faktoren
- **Medizinischer Kontext:** E-Mail-Account einer Arztpraxis potenziell
Patientendaten in Mails (DSG/DSGVO-relevant)
- **Expositionsdauer:** Unbekannt. Das Passwort war seit der Erstellung
der Konfigurationsdatei im Klartext gespeichert.
- **Zugriffskreis:** Alle Personen/Systeme mit Lesezugriff auf das
Projektverzeichnis, Backups oder Transfers dieser Datei.
---
## 5. Sofortmassnahmen
| Nr | Massnahme | Status | Verantwortlich |
|----|-----------|--------|----------------|
| 1 | Passwort aus Konfigurationsdatei entfernt | ERLEDIGT (STEP 4.4) | Security Engineer |
| 2 | Code umgestellt auf ENV-basierte Credentials | ERLEDIGT (STEP 4.4) | Security Engineer |
| 3 | Migrationswarnung implementiert | ERLEDIGT (STEP 4.4) | Security Engineer |
| 4 | **Passwort beim Mail-Provider rotieren** | **OFFEN DRINGEND** | Projektinhaber |
| 5 | Prüfung ob Account kompromittiert wurde | OFFEN | Projektinhaber |
---
## 6. Empfohlene Folgeaktionen
### Sofort (innerhalb 24h)
- [ ] **Passwort beim Mail-Provider ändern** (neues, starkes Passwort,
mindestens 16 Zeichen, keine Wiederverwendung)
- [ ] **2FA aktivieren**, falls der Mail-Provider dies unterstützt
- [ ] **Login-Protokolle prüfen**: Gibt es unbekannte Zugriffe auf den
betroffenen Mail-Account?
### Kurzfristig (innerhalb 1 Woche)
- [ ] **Passwort-Wiederverwendung prüfen**: Wurde dasselbe Passwort für
andere Dienste/Systeme verwendet? Falls ja: dort ebenfalls rotieren.
- [ ] **Backups durchsuchen**: Prüfen, ob Kopien der Datei
`aza_email_config.json` in Backups, Cloud-Speicher, USB-Sticks,
E-Mail-Anhängen oder anderen Transfers vorhanden sind.
Falls ja: Passwort dort ebenfalls als exponiert betrachten.
- [ ] **Projekt-Kopien prüfen**: Alle `backup *`-Ordner und Kopien
des Projektverzeichnisses auf Klartext-Passwörter durchsuchen.
### Mittelfristig (Empfehlung)
- [ ] **Secret-Scanning** in CI/CD-Pipeline einführen (z.B. git-secrets,
truffleHog, GitHub Secret Scanning), um zukünftige Klartext-Credentials
automatisch zu erkennen.
- [ ] **OS-Keychain-Integration** evaluieren (Windows Credential Manager),
um Passwörter sicher und benutzerfreundlich zu speichern.
---
## 7. Lessons Learned
### Ursache
Das Passwort wurde direkt im GUI-Dialog eingegeben und ohne
Schutzmassnahmen in eine JSON-Datei auf der Festplatte geschrieben.
Es gab keine Trennung zwischen Konfiguration und Credentials.
### Präventionsmassnahmen (implementiert)
1. **Credentials werden NIE mehr in Dateien geschrieben**
(`_strip_passwords()` in `save_email_config()`)
2. **Credentials kommen ausschliesslich aus ENV-Variablen**
(`AZA_EMAIL_PASSWORD_0`, `_1`, ...)
3. **Migrationswarnung** bei bestehenden Klartext-Passwörtern in der Config
4. **Klartext-Passwörter in JSON werden ignoriert** (kein Auto-Migrate)
### Präventionsmassnahmen (empfohlen, nicht implementiert)
1. Secret-Scanning in CI/CD
2. Pre-Commit-Hooks für Secret-Detection
3. OS-Keychain als sicherer Credential-Store
4. Regelmässige Audits der Konfigurationsdateien
---
## 8. Referenzen
- STEP 3 GAP-Analyse: Lücke #28 identifiziert
- STEP 4.4 Implementierung der Credential-Entfernung
- STEP 4.4 Handover: `/security/handovers/STEP_4_4_CREDENTIALS.md`
---
*Erstellt: 2026-02-22 im Rahmen des AZA/MedWork Security & Compliance Projects*
*Klassifikation: Intern Nicht zur externen Weitergabe bestimmt*

View File

@@ -0,0 +1,32 @@
# STEP 1 HIN TECHNICAL REFERENCE COLLECTION
# Status: ABGESCHLOSSEN
---
## Was gemacht wurde
- Ordner /security/reference/hin_docs/ erstellt
- Öffentlich verfügbare technische Dokumentation von HIN gesammelt
- 16 Originalquellen (hin.ch, support.hin.ch, cdn.hin.ch, msxfaq.de) ausgewertet
- Technische Referenzdatei erstellt: hin_architektur.md
- Quellenverzeichnis erstellt: hin_quellen.md
- Support-Anfrage vorbereitet: QUELLEN_UND_ANLEITUNG.md
## Was geändert wurde
- NEU: /security/reference/hin_docs/hin_architektur.md
- NEU: /security/reference/hin_docs/hin_quellen.md
- NEU: /security/reference/hin_docs/QUELLEN_UND_ANLEITUNG.md
Keine Codeänderungen. Keine Architekturänderungen.
## Offene Punkte
- Offizielle HIN-PDFs sind nicht frei downloadbar
- Support-Anfrage an support@hin.ch ist vorbereitet aber NICHT gesendet (muss User tun)
- HIN Gateway Dokumentation (ZIP) unter https://download.hin.ch/gw/hin-gateway.zip nicht heruntergeladen
## Risiken
- Alle Daten stammen aus öffentlichen Quellen, nicht aus offizieller HIN-Vertragsdokumentation
- Einige technische Details (TLS-Versionen, Cipher Suites) sind in öffentlichen Quellen nicht verfügbar

View File

@@ -0,0 +1,70 @@
# STEP 2 TECHNISCHE ZERLEGUNG VON HIN
# Status: ABGESCHLOSSEN
---
## Was gemacht wurde
Analyse der HIN-Dokumente aus /security/reference/hin_docs/.
Strukturierte Extraktion des HIN Security Stacks in 5 Bereiche.
## Ergebnis (Kurzfassung)
### 1. Transport
- TLS: optional, nicht erzwungen
- S/MIME ist primärer Transportschutz (Legacy)
- Wireguard für Stargate (neue Generation)
- SCION für SSHN-Netzwerk
- TLS-Version, Cipher Suites, PFS: NICHT BELEGT
### 2. Netzwerk
- Geschlossener Vertrauensraum (Gated Community)
- SCION/SSHN mit Schweizer-only Routing
- DDoS-Schutz durch Architektur
- 1 Instanz pro Organisation (Segmentierung)
- Zero Trust, IP-Whitelisting: NICHT BELEGT
### 3. PKI
- Eigene CA: "HIN MGW Issuing CA 2022"
- RSA 4096-bit, SHA256withRSA
- Domain-Zertifikate (10 Jahre Gültigkeit)
- SSHN-Zertifikate (72h Gültigkeit)
- Zentrale Verteilung, geschlossenes System
- Hardware-Token, Smartcard: NICHT BELEGT
### 4. Authentifizierung
- 2FA in drei Varianten (Client, Gateway, Mobil)
- Identitätsprüfung via Videoidentifikation
- SSO für HIN-geschützte Anwendungen
- eID-Typen: Persönlich, Organisation, Team
- SAML/OAuth/OIDC: NICHT BELEGT
### 5. Mail
- S/MIME Gateway-zu-Gateway (nicht Ende-zu-Ende)
- Domain-Zertifikate, automatische Verschlüsselung
- HIN Mail Global: GINA/SEPPMail, SMS-Auth, 30 Tage
- Betreffzeile bei HIN Mail Global: UNVERSCHLÜSSELT
- Backend: SEPPMail-Appliance, Zimbra (IMAP)
## Was geändert wurde
Keine Dateien geändert. Analyse wurde im Chat ausgegeben.
## Offene Punkte
1. TLS-Version, Cipher Suites, PFS nicht dokumentiert
2. mTLS unklar
3. SSO-Protokoll (SAML/OAuth/OIDC) nicht dokumentiert
4. Hardware-Token / Smartcard nicht dokumentiert
5. Client-Zertifikate vs. Software-Token nicht spezifiziert
6. Zero Trust Modell nicht explizit
7. Post-Quanten-Kryptografie nur "vorbereitet", keine Details
8. Wireguard-Konfiguration keine Details
9. Ende-zu-Ende Option nicht dokumentiert
10. IP-Whitelisting nicht dokumentiert
## Risiken
- 10 von 20+ technischen Parametern sind in öffentlichen Quellen NICHT BELEGT
- Gap-Analyse wird in diesen Bereichen auf konservative Annahmen angewiesen sein
- Ohne offizielle HIN-Dokumentation bleiben diese Lücken bestehen

View File

@@ -0,0 +1,209 @@
# =====================================================================
# STEP 3 FORMELLE GAP-ANALYSE (HIN vs. AZA)
# =====================================================================
# Datenquellen: /security/reference/hin_docs/ + Projekt-Code
# Methode: Nur technisch belegbare Fakten
# =====================================================================
## 1. Transport Security
### HIN:
- S/MIME als primärer Transportschutz (nicht TLS)
- TLS optional, nicht erzwungen auf SMTP-Ebene
- Wireguard-Protokoll für Stargate (App-zu-App, kein VPN)
- SCION-Netzwerk für SSHN (any-to-any Verschlüsselung)
- Routing ausschliesslich über Schweizer Netze
### AZA:
- SMTP mit STARTTLS (Port 587) aza_email.py Zeilen 14331436, 29942997
- IMAP über SSL/TLS (Port 993, IMAP4_SSL) aza_email.py Zeilen 1470, 1705
- Keine eigene TLS-Konfiguration (Python-Defaults)
- Keine Zertifikatsverifikation im Code gesetzt
- todo_server.py: reiner HTTP-Server, kein HTTPS Zeile 228
- transcribe_server.py: kein TLS
- backend_main.py: kein TLS
- Supabase-Zugriff über HTTPS aza_config.py Zeile 176
### GAP:
| Aspekt | HIN | AZA | Lücke |
|--------|-----|-----|-------|
| Mail-Verschlüsselung | S/MIME (RSA 4096) | Nur STARTTLS (opportunistisch) | JA |
| App-zu-App Transport | Wireguard | Kein verschlüsselter Kanal | JA |
| Netzwerk-Routing | SCION, nur CH | Internet, global | JA |
| Server-Kommunikation | Verschlüsselt | HTTP (Klartext) | JA |
| TLS-Konfiguration | UNBEKANNT | Python-Defaults | UNBEKANNT |
### Kritikalität: **HIGH**
---
## 2. Netzwerkmodell
### HIN:
- Geschlossener Vertrauensraum (Gated Community)
- SCION/SSHN mit AS-Nummern und Zertifikaten
- DDoS-Schutz durch Architektur
- 1 Instanz pro Organisation (keine Mandantenfähigkeit)
- Redundante Transportwege, automatischer Fail-over
- Governance: HIN, FMH, pharmaSuisse, BAG, SWITCH
### AZA:
- Kein Netzwerk-Perimeter definiert
- Kein VPN
- Kein DDoS-Schutz
- Keine Netzwerk-Segmentierung
- Einzige Netzwerk-Massnahme: Windows-Firewall-Regel für Todo-Server-Port todo_server.py Zeilen 209220
- Backend-Services auf localhost (HTTP)
- Supabase als externer Cloud-Service (HTTPS)
### GAP:
| Aspekt | HIN | AZA | Lücke |
|--------|-----|-----|-------|
| Vertrauensraum | Geschlossen, verifiziert | Offen, kein Perimeter | JA |
| DDoS-Schutz | SCION-basiert | Keiner | JA |
| Segmentierung | 1 Instanz/Organisation | Keine | JA |
| Redundanz | Multi-Path, Fail-over | Keine | JA |
| Governance | 5 Organisationen | Keine | JA |
| VPN | Wireguard (Stargate) | Keiner | JA |
### Kritikalität: **HIGH**
---
## 3. PKI
### HIN:
- Eigene CA: "HIN MGW Issuing CA 2022"
- RSA 4096-bit, SHA256withRSAEncryption
- Domain-Zertifikate (10 Jahre Gültigkeit)
- SSHN-Zertifikate (72h Gültigkeit, automatische Erneuerung)
- Zentrale Zertifikatsverteilung über HIN-Backend
- Geschlossenes System
### AZA:
- Keine PKI
- Keine eigene CA
- Keine Zertifikate (weder Client- noch Server-Zertifikate)
- Kein Zertifikatsmanagement
- Kein CSR-Prozess
- Keine Signierung von Daten oder Kommunikation
### GAP:
| Aspekt | HIN | AZA | Lücke |
|--------|-----|-----|-------|
| Eigene CA | HIN MGW Issuing CA 2022 | Keine | JA |
| Zertifikate | RSA 4096, Domain-Zerts | Keine | JA |
| Zertifikatsverteilung | Zentral, automatisch | Nicht vorhanden | JA |
| Schlüsselmanagement | Zentral über Backend | Nicht vorhanden | JA |
| Hardware-Token | UNBEKANNT | Nicht vorhanden | UNBEKANNT |
### Kritikalität: **HIGH**
---
## 4. Authentifizierung
### HIN:
- 2FA in drei Varianten:
- HIN Client: Passwort + kryptographischer Geräteschlüssel
- HIN Gateway: mTAN/Auth-App + Active Directory
- Mobil: mTAN/Auth-App + HIN Login+Passwort
- Identitätsprüfung via Videoidentifikation
- eID-Typen: Persönlich, Organisation, Team
- SSO für HIN-geschützte Anwendungen
- Berufsqualifikation über Register/Verbände verifiziert
### AZA:
- basis14.py: SHA-256 Passwort-Hash OHNE Salt Zeilen 12981300
- workforce_planner: bcrypt mit Salt auth.py Zeilen 2328
- workforce_planner: JWT mit HS256 auth.py Zeilen 3137
- workforce_planner: Secret Key hardcoded als Fallback "dev-secret-change-in-production" config.py Zeile 19
- workforce_planner: Token-Ablauf 480 Minuten (8h) config.py Zeile 20
- backend_main.py: API-Token via Header "X-Api-Token" Zeilen 154157
- backend_main.py: User-Identifikation via Header "X-User" Zeilen 162164
- Rollen: ADMIN, MANAGER, EMPLOYEE enums.py Zeilen 3336
- Rollenbasierte Zugriffskontrolle mit require_role() auth.py Zeilen 5966
- Keine 2FA / MFA / OTP
- Keine zertifikatsbasierte Authentifizierung
- Keine Identitätsprüfung (Videoidentifikation o.ä.)
- Kein SSO-Protokoll (SAML/OAuth/OIDC)
### GAP:
| Aspekt | HIN | AZA | Lücke |
|--------|-----|-----|-------|
| 2FA | Ja (3 Varianten) | Nein | JA |
| Passwort-Hashing | UNBEKANNT | SHA-256 ohne Salt (basis14) / bcrypt (workforce) | JA (basis14) |
| Secret Key | UNBEKANNT | Hardcoded Fallback | JA |
| Identitätsprüfung | Videoidentifikation | Keine | JA |
| SSO | Ja | Nein | JA |
| Zertifikats-Auth | Ja (Geräteschlüssel) | Nein | JA |
| Token-Ablauf | UNBEKANNT | 480 min (8h) | UNBEKANNT |
| Rollen | eID-basiert (3 Typen) | RBAC (3 Rollen) | TEILWEISE |
### Kritikalität: **HIGH**
---
## 5. Mail-Sicherheit
### HIN:
- S/MIME Gateway-zu-Gateway mit Domain-Zertifikaten (RSA 4096)
- Automatische Verschlüsselung/Entschlüsselung durch Gateway
- HIN Mail Global: GINA/SEPPMail für externe Empfänger
- SMS-Code-Authentifizierung für externe Empfänger
- Domain-Prüfung gegen HIN-Domainliste
- Betreffzeile bei HIN Mail Global: UNVERSCHLÜSSELT
- Header "X-HIN-Encrypted" für Steuerung
### AZA:
- SMTP mit STARTTLS (opportunistisch, nicht erzwungen) aza_email.py
- IMAP über SSL (IMAP4_SSL) aza_email.py
- Keine S/MIME-Implementierung
- Keine PGP/GPG-Implementierung
- Keine Gateway-Verschlüsselung
- Keine Ende-zu-Ende-Verschlüsselung
- Keine Signierung von E-Mails
- Passwörter im Klartext in aza_email_config.json gespeichert
- Keine Zertifikatsverifikation im SMTP/IMAP-Code
### GAP:
| Aspekt | HIN | AZA | Lücke |
|--------|-----|-----|-------|
| Mail-Verschlüsselung | S/MIME (RSA 4096) | Keine | JA |
| Gateway-Verschlüsselung | Automatisch | Keine | JA |
| Externe Empfänger | HIN Mail Global (GINA) | Keine Lösung | JA |
| Signierung | S/MIME Signatur | Keine | JA |
| Credential-Speicherung | UNBEKANNT | Klartext JSON | JA |
| Zertifikatsverifikation | Domain-Zertifikate | Python-Defaults | JA |
### Kritikalität: **HIGH**
---
## OFFENE TECHNISCHE FRAGEN
1. HIN: TLS-Version, Cipher Suites, PFS nicht in hin_docs belegt
2. HIN: Hardware-Token / Smartcard nicht in hin_docs belegt
3. HIN: SSO-Protokoll (SAML/OAuth/OIDC) nicht in hin_docs belegt
4. HIN: Passwort-Hashing-Verfahren nicht in hin_docs belegt
5. HIN: Token-Ablaufzeiten nicht in hin_docs belegt
6. HIN: Credential-Speicherung nicht in hin_docs belegt
7. AZA: Python-Default-TLS-Verhalten hängt von Systemkonfiguration ab nicht geprüft
8. AZA: Supabase-Sicherheitskonfiguration nicht im Scope dieser Analyse
9. AZA: Ob STARTTLS erzwungen oder opportunistisch ist Code zeigt keine Prüfung auf Erfolg
---
## ZUSAMMENFASSUNG
| Bereich | Kritikalität | Anzahl Lücken |
|---------|-------------|---------------|
| 1. Transport Security | HIGH | 5 |
| 2. Netzwerkmodell | HIGH | 6 |
| 3. PKI | HIGH | 5 |
| 4. Authentifizierung | HIGH | 7 |
| 5. Mail-Sicherheit | HIGH | 6 |
| **GESAMT** | **HIGH** | **29 identifizierte Lücken** |
Alle 5 Bereiche haben Kritikalität HIGH.
9 Punkte konnten mangels HIN-Dokumentation nicht vollständig verglichen werden.

View File

@@ -0,0 +1,54 @@
# STEP 3 FORMELLE GAP-ANALYSE (HIN vs. AZA)
# Status: ABGESCHLOSSEN
---
## Ziel des Schrittes
Formale Gegenüberstellung des HIN Security Stacks mit dem aktuellen
AZA/MedWork-Sicherheitszustand. Nur faktenbasierter Vergleich,
keine Empfehlungen, keine Implementierung.
## Konkrete Änderungen
- NEU: /security/handovers/STEP_03_GAP_ANALYSE.md (vollständige Gap-Analyse)
## Betroffene Dateien
Keine Dateien geändert. Nur Analyse-Dokument erstellt.
Analysierte Quelldateien:
- basis14.py (Passwort-Hashing, Login)
- aza_email.py (SMTP/IMAP, Mail-Sicherheit)
- aza_email_config.json (Credential-Speicherung)
- aza_config.py (Supabase-Zugriff)
- backend_main.py (API-Token, User-Header)
- todo_server.py (HTTP-Server, Firewall)
- transcribe_server.py (kein TLS)
- workforce_planner/api/auth.py (JWT, bcrypt)
- workforce_planner/config.py (Secret Key)
- workforce_planner/core/enums.py (Rollen)
- workforce_planner/core/models.py (Employee-Modell)
## Sicherheitsauswirkung
29 Lücken identifiziert, alle 5 Bereiche mit Kritikalität HIGH:
- Transport: 5 Lücken (kein S/MIME, kein verschlüsselter App-Kanal, HTTP-Server)
- Netzwerk: 6 Lücken (kein Perimeter, kein VPN, kein DDoS-Schutz)
- PKI: 5 Lücken (keine CA, keine Zertifikate, kein Schlüsselmanagement)
- Authentifizierung: 7 Lücken (keine 2FA, SHA-256 ohne Salt, hardcoded Secret)
- Mail: 6 Lücken (keine Verschlüsselung, Klartext-Passwörter in Config)
## Offene Punkte
- 9 Vergleichspunkte nicht vollständig bewertbar (HIN-Dokumentation fehlt)
- Supabase-Sicherheitskonfiguration nicht im Scope
- Python-Default-TLS-Verhalten systemabhängig
## Risiken
- Alle 5 Bereiche zeigen fundamentale Lücken zum HIN-Standard
- Besonders kritisch: Klartext-Passwörter in aza_email_config.json
- Besonders kritisch: SHA-256 ohne Salt in basis14.py
- Besonders kritisch: Hardcoded Secret Key "dev-secret-change-in-production"
- Besonders kritisch: HTTP-Server ohne TLS (todo_server, transcribe_server, backend)

View File

@@ -0,0 +1,139 @@
# STEP 10 Technische Einwilligungs-Protokollierung
## Ziel
Jede KI-bezogene Datenverarbeitung muss technisch nachweisbar
durch eine Einwilligung gedeckt sein.
---
## 1. Datenmodell
Jeder Consent-Eintrag enthält:
| Feld | Typ | Beschreibung |
|---|---|---|
| user_id | string | Benutzername aus Profil |
| consent_type | string | Immer "ai_processing" |
| consent_version | string | Stand-Datum aus ai_consent.md |
| timestamp | string | UTC ISO-8601 |
| source | string | "ui", "test", "admin" |
| action | string | "grant" oder "revoke" |
| prev_hash | string | SHA-256 des vorherigen Eintrags |
| hash | string | SHA-256 dieses Eintrags (Integritaetskette) |
## 2. Speicherort
- Datei: `aza_consent_log.json` (Projekt-Root)
- Format: JSON-Array (Append-only)
- Manipulationssicherheit: SHA-256-Hash-Kette
(jeder Eintrag referenziert den Hash des vorherigen)
- Kein Überschreiben: Widerruf erzeugt neuen Eintrag,
alter Grant bleibt in der Historie
## 3. Enforcement-Punkte
KI-Funktionen werden an folgenden Stellen blockiert, wenn
keine gueltige Einwilligung vorliegt:
| Stelle | Datei | Methode |
|---|---|---|
| Chat Completion (alle KI-Texte) | basis14.py | call_chat_completion() |
| Transkription | basis14.py | transcribe_wav() |
| Aufnahme starten | basis14.py | toggle_record() |
| Korrektur-Aufnahme | basis14.py | _toggle_record_append() |
| Diktat in Widget | basis14.py | _diktat_into_widget() |
| KI-Prüfung | basis14.py | open_ki_pruefen() |
| Interaktionscheck | basis14.py | do_interaktion() |
Bei fehlendem Consent:
- UI-Einstiegspunkte: Consent-Dialog wird angezeigt
- API-Gateways: RuntimeError wird geworfen
## 4. UI-Elemente
### 4.1 Consent-Dialog (vor erster KI-Nutzung)
- Zeigt den kompletten Text aus legal/ai_consent.md
- Checkbox: "Ich habe den Text gelesen und stimme zu"
- Buttons: "Zustimmen" / "Ablehnen"
- Wird nur angezeigt, wenn kein gueltiger Consent vorliegt
### 4.2 Einstellungsfenster (Datenschutz & Recht)
- Status-Anzeige: "Erteilt" / "Nicht erteilt / widerrufen"
- Button: "KI-Einwilligung widerrufen" / "erteilen"
- Button: "Consent-Log exportieren"
- Bestehende Buttons: Datenschutzerklaerung, KI-Einwilligung
## 5. Versions-Handling
- Die Consent-Version wird aus dem "Stand:"-Feld in
legal/ai_consent.md extrahiert
- Bei Aenderung des Consent-Textes (neues Datum) wird
ein bestehender Consent ungueltig
- Benutzer muss erneut zustimmen
## 6. Audit-Workflow
### Export
```
python -c "from aza_consent import export_consent_log; print(export_consent_log())"
```
Oder ueber Einstellungen -> "Consent-Log exportieren"
### Integritaetspruefung
```
python -c "from aza_consent import verify_chain_integrity; ok, e = verify_chain_integrity(); print('OK' if ok else e)"
```
### Export-Format
```json
{
"export_timestamp": "2026-02-22T...",
"total_entries": 3,
"current_consent_version": "Februar 2026",
"entries": [
{
"user_id": "...",
"consent_type": "ai_processing",
"consent_version": "Februar 2026",
"timestamp": "2026-02-22T...",
"source": "ui",
"action": "grant",
"prev_hash": "0000...0000",
"hash": "a1b2c3..."
}
]
}
```
## 7. Geaenderte / Neue Dateien
| Datei | Aktion |
|---|---|
| aza_consent.py | NEU Consent-Modul (Datenmodell, Storage, Enforcement, Export) |
| basis14.py | GEAENDERT Import aza_consent, Consent-Check an 7 Stellen |
| aza_settings_mixin.py | GEAENDERT Widerruf/Erteil-Button, Status, Export im Einstellungsfenster |
| _test_consent.py | NEU Testskript (14 Tests) |
## 8. Test-Ergebnisse (22.02.2026)
| Test | Ergebnis |
|---|---|
| Ohne Consent -> KI blockiert | PASS |
| Consent erteilen -> KI erlaubt | PASS |
| Widerruf -> KI blockiert | PASS |
| Erneuter Consent -> KI erlaubt | PASS |
| Hash-Kette intakt | PASS |
| Status-Abfrage korrekt | PASS |
| Version-Match | PASS |
| Export erstellt gueltige Datei | PASS |
| User-Historie korrekt | PASS |
| Anderer User -> kein Consent | PASS |
| Gesamt: 14/14 | ALLE BESTANDEN |
## 9. Risiken
| Risiko | Bewertung | Massnahme |
|---|---|---|
| JSON-Datei manuell editierbar | NIEDRIG | Hash-Kette erkennt Manipulation |
| Kein digitaler Signatur-Mechanismus | MITTEL | Fuer Audit: Export + Integritaetspruefung |
| Consent nur auf App-Ebene, nicht Patient | NIEDRIG | Papier-Einwilligung ergaenzt dies |

View File

@@ -0,0 +1,209 @@
# STEP 10a Consent Audit-Proof (Nachweis + Checkliste)
Datum: 22.02.2026
Pruefung durch: Internes Audit-Skript (_test_consent_audit.py)
---
## 1. Speicherort + Schema
Datei: aza_consent_log.json (Projekt-Root)
Format: JSON Array (Append-only, kein Ueberschreiben)
Schema pro Eintrag:
Feld Typ Beschreibung
-----------------------------------------------
user_id string Benutzer-ID aus Profil
consent_type string Immer "ai_processing"
consent_version string Stand-Datum aus ai_consent.md
timestamp string UTC ISO-8601
source string "ui" / "test" / "admin"
action string "grant" oder "revoke"
prev_hash string SHA-256 des vorherigen Eintrags
hash string SHA-256 dieses Eintrags
Versions-Mechanismus:
consent_version wird aus legal/ai_consent.md extrahiert.
Die Funktion _get_consent_version() liest die Zeile, die mit
"Stand:" beginnt, und gibt den Wert nach dem Doppelpunkt zurueck.
Aktuell: "Februar 2026"
Bei Aenderung des Datums in ai_consent.md wird jede bestehende
Einwilligung ungueltig und muss erneuert werden.
---
## 2. Beispiel-Logeintraege (sanitized)
2a) GRANT (Zustimmung):
{
"user_id": "user_1",
"consent_type": "ai_processing",
"consent_version": "Februar 2026",
"timestamp": "2026-02-22T21:13:25.419060+00:00",
"source": "ui",
"action": "grant",
"prev_hash": "000000000000000000000000000000000000000000000000...",
"hash": "6988dcc3fb7215af69eb1fcdd35afaf7aab37262b3ed..."
}
2b) REVOKE (Widerruf):
{
"user_id": "user_1",
"consent_type": "ai_processing",
"consent_version": "Februar 2026",
"timestamp": "2026-02-22T21:13:25.419060+00:00",
"source": "ui",
"action": "revoke",
"prev_hash": "6988dcc3fb7215af69eb1fcdd35afaf7aab37262b3ed...",
"hash": "2a60ef4a43cefc8e3c39dc155530c0f443bbb0b47b0e..."
}
2c) RE-GRANT (erneute Zustimmung):
{
"user_id": "user_1",
"consent_type": "ai_processing",
"consent_version": "Februar 2026",
"timestamp": "2026-02-22T21:13:25.421171+00:00",
"source": "ui",
"action": "grant",
"prev_hash": "2a60ef4a43cefc8e3c39dc155530c0f443bbb0b47b0e...",
"hash": "2cb429a75c3d02f8861c6fd6fbec1216acb6a453926a..."
}
Timestamp-Pruefung (UTC-Nachweis):
Alle Timestamps enden auf "+00:00" -> UTC bestaetigt.
---
## 3. Integritaetsbeweis
3a) Intakte Log-Datei:
verify_chain_integrity() -> PASS
Alle Hash-Werte stimmen mit der Berechnung ueberein.
Alle prev_hash-Referenzen sind korrekt verkettet.
3b) Manipulierte Log-Datei:
Manipulation: timestamp von Eintrag 0 um ein Zeichen geaendert
("...+00:00" -> "...+00:0X")
verify_chain_integrity() -> FAIL
Fehlermeldung:
"Eintrag 0: Hash stimmt nicht
(erwartet 8b92439ca6d1b926..., gefunden 6988dcc3fb7215af...)"
Ergebnis: Manipulation wird zuverlaessig erkannt.
3c) Nach Restore der Originaldatei:
verify_chain_integrity() -> PASS
---
## 4. Enforcement-Beweis
4a) Ohne Consent:
has_valid_consent("user_1") -> False
KI-Funktion: BLOCKIERT
Verhalten: RuntimeError "KI-Einwilligung fehlt oder wurde widerrufen."
UI: Consent-Dialog wird angezeigt (Checkbox + Zustimmen-Button)
4b) Nach Consent:
record_consent("user_1") ausgefuehrt
has_valid_consent("user_1") -> True
KI-Funktion: ERLAUBT
4c) Nach Widerruf:
record_revoke("user_1") ausgefuehrt
has_valid_consent("user_1") -> False
KI-Funktion: BLOCKIERT
4d) Version-Change (consent_version geaendert):
Vor Aenderung: has_valid_consent -> True
consent_version im Log manuell auf "Januar 2025" gesetzt
Aktuelle Version in ai_consent.md: "Februar 2026"
has_valid_consent -> False
Ergebnis: Erneute Zustimmung erforderlich (KORREKT)
Enforcement-Punkte im Code:
Datei Methode Typ
-------------------------------------------------------
basis14.py call_chat_completion() API-Gateway
basis14.py transcribe_wav() API-Gateway
basis14.py toggle_record() UI-Einstieg
basis14.py _toggle_record_append() UI-Einstieg
basis14.py _diktat_into_widget() UI-Einstieg
basis14.py open_ki_pruefen() UI-Einstieg
basis14.py do_interaktion() UI-Einstieg
---
## 5. Data Minimization Check
Gespeicherte Felder im Consent-Log (vollstaendige Liste):
action, consent_type, consent_version, hash,
prev_hash, source, timestamp, user_id
Pruefung auf sensible Daten:
Transkript-Inhalte: NICHT gespeichert
KG-Inhalte: NICHT gespeichert
Prompts / KI-Antworten: NICHT gespeichert
API-Keys / Secrets: NICHT gespeichert
Passwoerter: NICHT gespeichert
Patientennamen: NICHT gespeichert
Diagnosen: NICHT gespeichert
Audio-Daten: NICHT gespeichert
Kein Feldwert ueberschreitet 200 Zeichen
(ausser hash/prev_hash mit genau 64 Hex-Zeichen).
Data Minimization: PASS
---
## 6. Gesamtbewertung
Pruefpunkt Ergebnis
-------------------------------------------------------
Speicherort + Schema PASS
Beispiel-Logeintraege PASS
Timestamp UTC PASS
Integritaet intakt PASS
Integritaet manipuliert erkannt PASS
Enforcement ohne Consent PASS
Enforcement mit Consent PASS
Enforcement nach Widerruf PASS
Version-Change erfordert Neu-Consent PASS
Data Minimization PASS
GESAMTBEWERTUNG: PASS (10/10)
---
## 7. Reproduzierbarkeit
Der Nachweis kann jederzeit reproduziert werden mit:
python _test_consent_audit.py
Das Skript erzeugt temporaere Testdaten, fuehrt alle Pruefungen
durch und raeumt danach auf (keine persistenten Aenderungen).
---
## 8. Offene Punkte
- Kein digitaler Signatur-Mechanismus (nur Hash-Kette):
Fuer hoechste Anforderungen waere eine kryptografische
Signatur (z.B. RSA/ECDSA) empfehlenswert.
- Consent bezieht sich auf den Anwendungsbenutzer (Arzt),
nicht auf den Patienten. Die Patienten-Einwilligung erfolgt
ueber das Papierformular (legal/ai_consent.md).

View File

@@ -0,0 +1,151 @@
# STEP 11 Audit-Logging (Minimal & DSG-konform)
## Ziel
Sicherheitsrelevante Ereignisse nachvollziehbar protokollieren,
ohne sensible Daten (Patientendaten, Prompts, Passwoerter) zu speichern.
---
## 1. Architektur
Neues Modul: aza_audit_log.py
Log-Format: Pipe-separierte Textdatei (eine Zeile pro Ereignis)
TIMESTAMP | EVENT | USER | STATUS | SOURCE | DETAIL
Beispiel:
2026-02-22T21:13:25.419+00:00 | LOGIN_OK | Dr. Mueller | OK | desktop |
Alle Timestamps sind UTC (ISO-8601).
---
## 2. Protokollierte Ereignisse
Event-Typ Beschreibung Wann
------------------------------------------------------------------
APP_START Anwendung gestartet main()
APP_STOP Anwendung beendet nach mainloop()
LOGIN_OK Erfolgreicher Login do_login()
LOGIN_FAIL Fehlgeschlagener Login do_login()
2FA_OK 2FA-Verifizierung erfolgreich do_verify()
2FA_FAIL 2FA-Verifizierung fehlgeschlagen do_verify()
CONSENT_GRANT KI-Einwilligung erteilt on_accept()
CONSENT_REVOKE KI-Einwilligung widerrufen toggle_consent()
AI_TRANSCRIBE KI-Transkription gestartet transcribe_wav()
AI_CHAT KI-Chat-Completion aufgerufen call_chat_completion()
AI_BLOCKED KI-Aufruf ohne Consent blockiert transcribe/chat
PASSWORD_REHASH Legacy-Hash auf bcrypt migriert do_login()
EXPORT Log-Export durchgefuehrt do_export()
---
## 3. Data Minimization (DSG Art. 6)
Was NICHT im Audit-Log gespeichert wird:
- Patientennamen / Patientendaten
- Transkripte / KG-Inhalte
- KI-Prompts / KI-Antworten
- Passwoerter / Passwort-Hashes
- API-Keys / Secrets
- E-Mail-Inhalte
Detail-Feld: Maximal 200 Zeichen, nur Metadaten
(z.B. "model=gpt-5.2", "2FA backup-code").
---
## 4. Log-Rotation
Parameter Standard ENV-Variable
--------------------------------------------------
Max. Dateigroesse 10 MB AZA_AUDIT_ROTATE_MB
Rotierte Dateien 12 AZA_AUDIT_KEEP
Logdatei-Pfad aza_audit.log AZA_AUDIT_LOG
Bei Ueberschreitung der Maximalgroesse:
aza_audit.log -> aza_audit.1.log -> ... -> aza_audit.12.log
Aelteste Datei wird ueberschrieben.
---
## 5. Export
Ueber Einstellungen -> "Logs exportieren (Audit)":
Exportiert sowohl Consent-Log als auch Audit-Log als JSON.
Programmatisch:
python -c "from aza_audit_log import export_audit_log; print(export_audit_log())"
Export-Format:
{
"export_timestamp": "2026-02-22T...",
"total_entries": 12,
"source_file": ".../aza_audit.log",
"entries": [
{
"timestamp": "2026-02-22T...",
"event": "LOGIN_OK",
"user_id": "...",
"status": "OK",
"source": "desktop",
"detail": ""
}
]
}
---
## 6. Integration bestehender Audit-Systeme
Komponente Audit-System Speicherort
-----------------------------------------------------------
Desktop-App (basis14) aza_audit_log.py aza_audit.log
Backend (backend_main) _audit_write() medwork_audit.log
Workforce Planner log_action() + DB workforce_planner.db
Alle drei Systeme sind unabhaengig und koennen
separat exportiert werden.
---
## 7. Geaenderte / Neue Dateien
Datei Aktion
--------------------------------------------------
aza_audit_log.py NEU Audit-Log-Modul
basis14.py GEAENDERT Import + 10 log_event()-Aufrufe
aza_settings_mixin.py GEAENDERT Import + Widerruf-Log + Export
_test_audit_log.py NEU Testskript (23 Tests)
---
## 8. Test-Ergebnisse (22.02.2026)
Test Ergebnis
-------------------------------------------
Events schreiben PASS
12 Zeilen korrekt PASS
6 Felder pro Zeile PASS
Timestamp UTC PASS
Event-Typ korrekt PASS
User korrekt PASS
Status OK/FAIL korrekt PASS
Source = desktop PASS
Statistiken korrekt PASS
Export JSON korrekt PASS
Data Minimization PASS
Pipe-Sanitierung PASS
Gesamt: 23/23 ALLE BESTANDEN
---
## 9. Risiken
Risiko Bewertung Massnahme
--------------------------------------------------------
Log-Datei loeschbar NIEDRIG Backup sichert mit
Kein zentrales SIEM MITTEL Export fuer manuelle Pruefung
Keine Echtzeit-Alarmierung MITTEL Manueller Review empfohlen
Log-Rotation verliert alte Daten NIEDRIG 12 Dateien a 10 MB = 120 MB

View File

@@ -0,0 +1,197 @@
STEP 11a AUDIT-LOG INTEGRITAET (HASH-KETTE)
Datum: 2026-02-22
Status: ABGESCHLOSSEN
=====================================================================
1. ZIEL
=====================================================================
Audit-Log (aza_audit_log.py) manipulations-erkennbar machen
durch SHA-256-Hash-Kette, analog zum Consent-Log.
=====================================================================
2. AENDERUNGEN
=====================================================================
Datei: aza_audit_log.py
a) Neues Format (8 Felder statt 6):
TIMESTAMP | EVENT | USER | STATUS | SOURCE | DETAIL | PREV_HASH | ENTRY_HASH
b) Hash-Berechnung:
entry_hash = SHA-256(prev_hash + payload)
payload = "TS | EVENT | USER | STATUS | SOURCE | DETAIL"
Startwert (GENESIS): 64x "0"
c) Rotation mit Ketten-Uebergabe:
Bei Rotation wird der letzte Hash des rotierten Files
als Header in der neuen Datei gespeichert:
#CHAIN_FROM=<letzter_entry_hash>
Neue Eintraege setzen prev_hash = Chain-Header-Hash.
Ein Auditor kann die Kette ueber Dateigrenzen pruefen.
d) Neue Funktionen:
- verify_integrity(path) -> (bool, list[str])
Prueft eine einzelne Logdatei auf Ketten-Integritaet.
- verify_all_rotations() -> (bool, dict)
Prueft alle rotierten Dateien.
e) CLI-Interface:
python -m aza_audit_log verify -> Aktuelle Datei pruefen
python -m aza_audit_log verify --all -> Alle Rotationsdateien
python -m aza_audit_log verify --file X -> Bestimmte Datei
python -m aza_audit_log stats -> Statistiken + Integritaet
python -m aza_audit_log export -> JSON-Export mit Integritaet
f) Export enthaelt jetzt:
- integrity: PASS/FAIL
- integrity_errors: []
- Jeder Entry hat prev_hash und entry_hash
g) get_log_stats() enthaelt jetzt:
- integrity: PASS/FAIL
=====================================================================
3. ROTATIONS-STRATEGIE
=====================================================================
Ablauf bei Rotation (Datei > AZA_AUDIT_ROTATE_MB):
1. Letzten entry_hash der aktuellen Datei auslesen
2. Bestehende .1, .2, ... Dateien hochschieben
3. Aktuelle Datei -> .1 verschieben
4. Neue leere Datei anlegen mit Header:
#CHAIN_FROM=<letzter_hash>
5. Neue Eintraege nutzen diesen Hash als prev_hash
Verifizierung:
- verify_integrity() pro Datei: prueft interne Kette
- Header #CHAIN_FROM verbindet die Dateien logisch
- verify_all_rotations() prueft alle Dateien der Reihe nach
=====================================================================
4. BEISPIEL LOG-ZEILE
=====================================================================
2026-02-22T21:42:27.160+00:00 | APP_START | user_1 | OK | desktop | test | 000000...0000 | a3f8e1...
Felder:
[0] Timestamp (UTC, ISO-8601)
[1] Event-Typ
[2] User-ID
[3] Status (OK/FAIL)
[4] Source
[5] Detail (max 200 Zeichen, keine sensiblen Daten)
[6] prev_hash (SHA-256 des vorherigen Eintrags)
[7] entry_hash (SHA-256 ueber prev_hash + Felder 0-5)
=====================================================================
5. VERIFY-ANLEITUNG + BEISPIELAUSGABEN
=====================================================================
a) Integritaet pruefen (CLI):
$ python -m aza_audit_log verify
Datei: C:\...\aza_audit.log
Integritaet: PASS
b) Manipulation erkennen:
$ python -m aza_audit_log verify
Datei: C:\...\aza_audit.log
Integritaet: FAIL
Zeile 2: entry_hash stimmt nicht (erwartet 104ebe9a..., gefunden 32e00f09...)
c) Alle Rotationsdateien:
$ python -m aza_audit_log verify --all
aza_audit.1.log: PASS
aza_audit.log: PASS
GESAMT: PASS
d) Statistiken:
$ python -m aza_audit_log stats
Datei: aza_audit.log
Existiert: True
Groesse: 0.0 MB
Eintraege: 2
Integritaet: PASS
=====================================================================
6. TEST-ERGEBNIS (PROOF)
=====================================================================
Testskript: _test_audit_integrity.py
21 Tests, 0 Fehler.
Tests:
1. Hash-Kette schreiben + verifizieren:
- 5 Eintraege geschrieben, verify PASS
- 8 Felder pro Zeile
- prev_hash[0] = GENESIS
- prev_hash[n] = entry_hash[n-1]
2. Manipulation erkennen:
- 1 Zeichen geaendert (LOGIN_OK -> LOGIN_XX)
- verify -> FAIL, Fehlerstelle gemeldet
- Restore -> PASS
3. Rotation mit Ketten-Uebergabe:
- Rotierte Datei (.1) intakt: PASS
- Aktuelle Datei mit Chain-Header: PASS
- Header-Hash == letzter Hash der rotierten Datei: PASS
- prev_hash im neuen File == Chain-Header: PASS
- verify_all_rotations: PASS
4. Stats + Export:
- Integritaet in Stats: PASS
- Export integrity: PASS
- Entries mit entry_hash: PASS
5. Data Minimization:
- Kein Passwort, kein API-Key, kein Transkript: PASS
=====================================================================
7. DATA MINIMIZATION
=====================================================================
Unveraendert gegenueber STEP 11:
- Keine Patientennamen
- Keine Transkripte/Prompts
- Keine Passwoerter/API-Keys
- Detail max. 200 Zeichen, nur Metadaten
=====================================================================
8. BETROFFENE DATEIEN
=====================================================================
Geaendert:
- aza_audit_log.py (Format erweitert, Hash-Kette, verify, CLI)
Neu:
- _test_audit_integrity.py (Proof-Skript)
- security/handovers/STEP_11a_AUDIT_LOG_INTEGRITY.md (dieses Dokument)
Nicht geaendert:
- basis14.py (Aufrufe bleiben kompatibel, neue Felder transparent)
- aza_settings_mixin.py (unveraendert)
- aza_consent.py (unveraendert)
=====================================================================
9. RISIKEN
=====================================================================
- Performance: _get_last_hash() liest die gesamte Datei.
Bei sehr grossen Dateien (kurz vor Rotation) kann dies langsam sein.
Akzeptabel, da Rotation bei 10 MB greift.
- Rueckwaertskompatibilitaet: Bestehende 6-Feld-Logdateien werden
von verify_integrity() als ungueltig gemeldet (< 8 Felder).
Loesung: Bestehende Logdatei vor erstem Start archivieren/loeschen.
=====================================================================
10. OFFENE PUNKTE
=====================================================================
Keine.

View File

@@ -0,0 +1,211 @@
STEP 12 MONITORING & HEALTH CHECKS (MINIMAL)
Datum: 2026-02-22
Status: ABGESCHLOSSEN
=====================================================================
1. ZIEL
=====================================================================
Minimaler Betriebsnachweis:
- Services leben (Health-Checks)
- Metriken aus bestehenden Logs (kein externer Dienst)
- Integritaetspruefung automatisierbar
- Scheduling dokumentiert
Keine Patientendaten erfasst.
=====================================================================
2. HEALTH-CHECK ENDPOINTS
=====================================================================
Alle Services liefern jetzt unter /health:
{
"status": "ok",
"version": "<app-version>",
"uptime_s": <seconds>,
"tls": true/false
}
a) backend_main.py
URL: https://127.0.0.1:8000/health
Vorher: {"ok": true}
Nachher: {"status": "ok", "version": "0.1.0", "uptime_s": ..., "tls": ...}
b) transcribe_server.py
URL: https://127.0.0.1:8090/health
Vorher: {"status": "ok"}
Nachher: {"status": "ok", "version": "0.1.0", "uptime_s": ..., "tls": ...}
c) todo_server.py
URL: https://127.0.0.1:5111/health
Vorher: Kein /health Endpoint
Nachher: {"status": "ok", "version": "1.0.0", "uptime_s": ..., "tls": ...}
d) Desktop-App (basis14.py)
Kein HTTP-Endpoint (Desktop-App ohne eigenen Server).
Betriebsnachweis ueber Audit-Log:
- APP_START / APP_STOP Events
- LOGIN_OK / LOGIN_FAIL Events
=====================================================================
3. MONITORING-METRIKEN (aza_monitoring.py)
=====================================================================
Quellen: Nur bestehende lokale Logs/Dateien. Kein Cloud-Dienst.
a) Audit-Log Metriken (aus aza_audit_log.py):
- Anzahl LOGIN_FAIL
- Anzahl AI_CHAT + AI_TRANSCRIBE (KI-Calls Zaehler)
- Anzahl AI_BLOCKED
- Anzahl 2FA_FAIL
- Integritaets-Status (PASS/FAIL)
b) Consent-Log Metriken (aus aza_consent.py):
- Anzahl Eintraege
- Integritaets-Status
c) Backup-Metriken (aus backups/ Verzeichnis):
- Anzahl vorhandener Backups
- Letztes Backup (Name + Zeitpunkt)
d) Alert-Severity-Stufen:
- INFO: Zaehler ohne Handlungsbedarf
- WARN: Erhoehte Werte (z.B. > 0 login_fail)
- HIGH: Kritische Schwellen (z.B. >= 10 login_fail)
- CRITICAL: Integritaets-Verletzung
=====================================================================
4. INTEGRITAETS-CHECKS
=====================================================================
Automatisierte Pruefung ueber:
python aza_monitoring.py integrity
Prueft:
- aza_audit_log: verify_all_rotations() (SHA-256 Hash-Kette)
- aza_consent_log: verify_chain_integrity() (SHA-256 Hash-Kette)
Bei Fehler:
- Klarer Log-Eintrag (INTEGRITY_FAIL Event ins Audit-Log)
- Exit-Code 1 (fuer Scheduler-Alerting)
=====================================================================
5. CLI-KOMMANDOS
=====================================================================
python aza_monitoring.py health Health-Checks aller Services
python aza_monitoring.py metrics Metriken aus Logs
python aza_monitoring.py integrity Integritaetspruefung (Exit 0/1)
python aza_monitoring.py alerts Sicherheits-Alerts
python aza_monitoring.py nightly Naechtlicher Gesamtcheck + JSON-Report
python aza_monitoring.py all Alles anzeigen
=====================================================================
6. SCHEDULING-BEISPIELE
=====================================================================
a) Linux (cron):
# Naechtlicher Gesamtcheck um 02:00
0 2 * * * cd /pfad/zu/aza && python aza_monitoring.py nightly >> /var/log/aza_monitoring.log 2>&1
# Stuendlicher Health-Check
0 * * * * cd /pfad/zu/aza && python aza_monitoring.py health >> /var/log/aza_health.log 2>&1
# Integritaet alle 6 Stunden
0 */6 * * * cd /pfad/zu/aza && python aza_monitoring.py integrity || echo "INTEGRITY FAIL" | mail admin@example.com
b) Windows (Task Scheduler):
Aktion: Programm starten
Programm: python
Argumente: aza_monitoring.py nightly
Starten in: C:\Users\surov\Documents\AZA\backup 19.2.26
Trigger: Taeglich, 02:00 Uhr
Alternativ via PowerShell:
$action = New-ScheduledTaskAction `
-Execute "python" `
-Argument "aza_monitoring.py nightly" `
-WorkingDirectory "C:\Users\surov\Documents\AZA\backup 19.2.26"
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"
Register-ScheduledTask -TaskName "AZA Nightly Monitor" `
-Action $action -Trigger $trigger -Description "AZA Nightly Monitoring"
=====================================================================
7. DATENSCHUTZ-HINWEIS (DATA MINIMIZATION)
=====================================================================
Das Monitoring erfasst KEINE:
- Patientennamen oder -daten
- Transkript-Inhalte oder Prompts
- Passwoerter oder API-Keys
- KI-Antworten
Es werden ausschliesslich Zaehler und Status-Informationen erhoben:
- Anzahl Events pro Typ
- PASS/FAIL Status
- Dateigeroessen und Zeitstempel
- Service-Version und Uptime
Health-Check-Responses enthalten nur technische Metadaten.
=====================================================================
8. TEST-ERGEBNIS
=====================================================================
Testskript: _test_monitoring.py
27 Tests, 0 Fehler.
Tests:
1. Metriken: Audit-Log Eintraege, Integritaet, Backup-Count
2. Alerts: login_fail, ai_calls_total, ai_blocked, 2fa_fail erkannt
3. Integritaets-Check: PASS bei intaktem Log
4. Manipulation: FAIL bei manipuliertem Log, PASS nach Restore
5. Nightly-Report: JSON-Struktur korrekt, Datei geschrieben
6. Data Minimization: Keine Passwoerter/Keys/Transkripte
7. Health-Check Format: _APP_VERSION und _START_TIME vorhanden
=====================================================================
9. BETROFFENE DATEIEN
=====================================================================
Geaendert:
- backend_main.py (/health erweitert: version, uptime_s, tls)
- transcribe_server.py (/health erweitert: version, uptime_s, tls)
- todo_server.py (/health neu hinzugefuegt)
Neu:
- aza_monitoring.py (Monitoring, Metriken, Integrity, CLI)
- _test_monitoring.py (Proof-Skript)
Nicht geaendert:
- basis14.py
- aza_audit_log.py (unveraendert, wird nur gelesen)
- aza_consent.py (unveraendert, wird nur gelesen)
=====================================================================
10. RISIKEN
=====================================================================
- Health-Checks sind unauthentifiziert.
Risiko: Gering (nur Status-Info, keine sensiblen Daten).
Massnahme: Bei Bedarf hinter API-Token schuetzen.
- Monitoring laeuft lokal, kein externes Alerting.
Risiko: Alerts werden nur im JSON-Report gespeichert.
Massnahme: Nightly-Report per Mail weiterleiten (Empfehlung).
- Kein Real-Time-Monitoring.
Risiko: Zwischenzeitliche Ausfaelle werden erst beim naechsten
Scheduled-Check erkannt.
Massnahme: Health-Check-Intervall anpassen (z.B. alle 5 Min).
=====================================================================
11. OFFENE PUNKTE
=====================================================================
Keine.

View File

@@ -0,0 +1,100 @@
# STEP 4.1 TLS-PFLICHT FÜR ALLE BACKEND-SERVICES
# Status: ABGESCHLOSSEN
---
## Ziel
GAP #4: Alle Backend-Services von HTTP auf HTTPS/TLS umstellen.
Fail-Start bei fehlendem Zertifikat im Produktionsmodus.
## Geänderte Dateien
| Datei | Änderung |
|-------|----------|
| **aza_tls.py** | NEU Zentrale TLS-Konfiguration |
| **todo_server.py** | TLS-Integration via create_ssl_context() |
| **transcribe_server.py** | TLS-Integration via uvicorn SSL-Kwargs |
| **backend_main.py** | TLS-Integration via uvicorn SSL-Kwargs |
## TLS-Parameter
| Parameter | Wert |
|-----------|------|
| Minimale TLS-Version | 1.2 (konfigurierbar via ENV) |
| Maximale TLS-Version | 1.3 |
| Cipher Suites | ECDHE+AESGCM, ECDHE+CHACHA20, DHE+AESGCM, DHE+CHACHA20 |
| Ausgeschlossen | aNULL, eNULL, MD5, DSS, RC4, 3DES |
| Perfect Forward Secrecy | Ja (nur ECDHE/DHE Cipher erlaubt) |
| DEV-Zertifikat | RSA 4096-bit, SHA-256, 365 Tage, Self-Signed |
| PROD-Vorbereitung | Pfade über ENV-Variablen konfigurierbar (ACME/Let's Encrypt) |
## Umgebungsvariablen
| Variable | Default | Beschreibung |
|----------|---------|--------------|
| AZA_TLS_CERTFILE | (leer) | Pfad zum Zertifikat (PEM) |
| AZA_TLS_KEYFILE | (leer) | Pfad zum Private Key (PEM) |
| AZA_TLS_MIN_VERSION | "1.2" | Minimale TLS-Version ("1.2" oder "1.3") |
| AZA_TLS_REQUIRE | "1" | "1" = Fail-Start ohne Zertifikat |
## Betroffene Server
| Server | Port | Typ | TLS-Methode |
|--------|------|-----|-------------|
| todo_server | 5111 | stdlib HTTPServer | ssl.wrap_socket() |
| transcribe_server | 8090 | FastAPI/Uvicorn | uvicorn ssl_certfile/ssl_keyfile |
| backend_main | 8000 | FastAPI/Uvicorn | uvicorn ssl_certfile/ssl_keyfile |
| workforce_planner | variabel | FastAPI/Uvicorn | Nicht geändert (kein __main__-Block) |
## Testmethode
### DEV-Zertifikat erstellen:
```
python aza_tls.py
```
### Server starten:
```
set AZA_TLS_CERTFILE=dev-cert.pem
set AZA_TLS_KEYFILE=dev-key.pem
python backend_main.py
```
### Testen:
```
curl https://localhost:8000/health --insecure
```
### Fail-Start testen (ohne Zertifikat):
```
set AZA_TLS_REQUIRE=1
python backend_main.py
→ Muss mit Fehlermeldung abbrechen
```
### DEV-Modus ohne TLS:
```
set AZA_TLS_REQUIRE=0
python backend_main.py
→ Startet auf HTTP mit Warnung
```
## Rest-Risiken
1. **workforce_planner**: Hat keinen eigenen __main__-Block. TLS muss beim
Start via uvicorn CLI konfiguriert werden:
`uvicorn workforce_planner.api.app:app --ssl-certfile=... --ssl-keyfile=...`
2. **Self-Signed Zertifikat**: Browser und Clients zeigen Warnungen.
Für Produktion muss ein CA-signiertes Zertifikat verwendet werden.
3. **Client-Seite**: Die Clients (basis14.py, aza_diktat_mixin.py) müssen
ihre Server-URLs von http:// auf https:// ändern das ist ein separater
Schritt.
4. **Zertifikatsrotation**: Kein automatischer Erneuerungsmechanismus
implementiert. Für PROD wird ACME/certbot empfohlen.
5. **todo_server PWA**: Service Worker erfordert HTTPS für vollständige
Funktionalität auf iPhones dies ist jetzt möglich.

View File

@@ -0,0 +1,157 @@
# STEP 4.1a TLS-ENFORCEMENT VERIFIKATION (BEWEIS)
# Status: ABGESCHLOSSEN
# Datum: 2026-02-22
---
## 1. Listener-Tabelle
| Server | Port | Host | Protokoll (mit TLS) | Protokoll (ohne TLS) |
|--------|------|------|---------------------|----------------------|
| backend_main | 8000 (ENV PORT) | 0.0.0.0 | HTTPS only | Fail-Start (AZA_TLS_REQUIRE=1) |
| transcribe_server | 8090 (ENV TRANSCRIBE_PORT) | 0.0.0.0 | HTTPS only | Fail-Start (AZA_TLS_REQUIRE=1) |
| todo_server | 5111 | 0.0.0.0 | HTTPS only | Fail-Start (AZA_TLS_REQUIRE=1) |
| workforce_planner | variabel | variabel | Über uvicorn CLI | Kein eigener __main__ |
---
## 2. Test-Kommandos und Ergebnisse
### TEST 1: Fail-Start ohne Zertifikat (AZA_TLS_REQUIRE=1)
**Kommando:**
```
$env:AZA_TLS_CERTFILE = ""
$env:AZA_TLS_KEYFILE = ""
$env:AZA_TLS_REQUIRE = "1"
$env:MEDWORK_API_TOKEN = "test123"
python backend_main.py
```
**Ergebnis:**
```
FEHLER: TLS ist erforderlich (AZA_TLS_REQUIRE=1), aber
AZA_TLS_CERTFILE und/oder AZA_TLS_KEYFILE sind nicht gesetzt.
```
Exit Code: 1
**Bewertung: PASS** Server startet nicht ohne Zertifikat.
---
### TEST 2: HTTPS-Verbindung
**Kommando:**
```python
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
r = urllib.request.urlopen('https://127.0.0.1:8444/health', context=ctx)
```
**Ergebnis:**
```
HTTPS: 200 {"ok": true}
```
**Bewertung: PASS** HTTPS funktioniert.
---
### TEST 3: HTTP-Verbindung (muss fehlschlagen)
**Kommando:**
```python
r = urllib.request.urlopen('http://127.0.0.1:8444/health', timeout=5)
```
**Ergebnis:**
```
http.client.RemoteDisconnected: Remote end closed connection without response
```
Exit Code: 1
**Bewertung: PASS** HTTP wird abgelehnt. Kein HTTP-Fallback.
---
### TEST 4: TLS-Version und Cipher
**Kommando:**
```python
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
with ctx.wrap_socket(socket.socket(), server_hostname='localhost') as s:
s.connect(('127.0.0.1', 8444))
print(s.version(), s.cipher())
```
**Ergebnis:**
```
TLS Version: TLSv1.3
Cipher: TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
Bits: 256
```
**Bewertung: PASS** TLS 1.3 mit AES-256-GCM, PFS aktiv.
---
### TEST 5: Zertifikat-Details
**Kommando:**
```python
cert = s.getpeercert(binary_form=True)
c = x509.load_der_x509_certificate(cert)
```
**Ergebnis:**
```
Subject: CN=localhost,O=AZA MedWork DEV
Issuer: CN=localhost,O=AZA MedWork DEV
Key: 4096 bit RSA
Algo: SHA-256
Valid: 2026-02-22 -> 2027-02-22
```
**Bewertung: PASS** RSA 4096-bit, SHA-256, Self-Signed (DEV).
---
### TEST 6: ENV-Hardcoding-Prüfung
**Kommando:** Grep nach "dev-cert.pem" / "dev-key.pem" in *.py
**Ergebnis:**
Nur in `aza_tls.py` Zeilen 114-115 (Generierungsfunktion) und Docstring.
NICHT in todo_server.py, transcribe_server.py, backend_main.py.
Alle Server lesen Zertifikatspfade ausschliesslich aus ENV-Variablen.
**Bewertung: PASS** Keine hardcodierten Zertifikatspfade.
---
## 3. Fix während Verifikation
| Datei | Zeile | Problem | Fix |
|-------|-------|---------|-----|
| aza_tls.py | 93-97 | `ssl_version: ssl.TLSVersion.TLSv1_2` inkompatibel mit uvicorn (ValueError: invalid protocol version 771) | Ersetzt durch `ssl_ciphers: _STRONG_CIPHERS` |
---
## 4. Gesamtbewertung
| Test | Ergebnis |
|------|----------|
| Fail-Start ohne Cert | **PASS** |
| HTTPS-Verbindung | **PASS** |
| HTTP-Ablehnung | **PASS** |
| TLS >= 1.2 | **PASS** (TLS 1.3 verhandelt) |
| Starke Cipher | **PASS** (AES-256-GCM) |
| PFS | **PASS** (ECDHE/DHE only) |
| Zertifikat RSA 4096 | **PASS** |
| Keine hardcodierten Pfade | **PASS** |
**GESAMTERGEBNIS: PASS (8/8)**

View File

@@ -0,0 +1,99 @@
# STEP 4.2 SICHERES PASSWORT-HASHING
# Status: ABGESCHLOSSEN
---
## Ziel
GAP #18: SHA-256 ohne Salt durch moderne KDF ersetzen.
Transparente Migration bestehender Hashes ohne Forced Reset.
## Alte vs. neue Methode
| Parameter | ALT (unsicher) | NEU (sicher) |
|-----------|----------------|--------------|
| Algorithmus | SHA-256 | bcrypt |
| Salt | Keines | Automatisch (eingebettet) |
| Cost-Faktor | n/a | 12 Runden |
| Hash-Format | 64 Hex-Zeichen | $2b$12$... (60 Zeichen) |
| Brute-Force-Resistenz | Minimal (GPU-optimiert) | ~250ms pro Versuch |
| Rainbow-Table-Schutz | Keiner | Salt schützt |
## Parameter
```
bcrypt.gensalt(rounds=12)
```
- Cost: 12 (2^12 = 4096 Iterationen)
- Salt: 16 Bytes, automatisch generiert
- Output: 60-Zeichen-String mit eingebettetem Salt + Params
- Format: $2b$12$<22-char-salt><31-char-hash>
## Geänderte Dateien
| Datei | Zeile | Änderung |
|-------|-------|----------|
| basis14.py | 16-17 | `import bcrypt` hinzugefügt |
| basis14.py | 1298-1300 | `_hash_password()`: SHA-256 → bcrypt |
| basis14.py | 1302-1311 | NEU: `_verify_password()` mit Legacy-Erkennung |
| basis14.py | 1313-1317 | NEU: `_is_legacy_hash()` |
| basis14.py | 1355-1362 | Login: `_verify_password()` + automatischer Rehash |
| basis14.py | 1459 | Registration: nutzt neuen `_hash_password()` (bcrypt) |
| basis14.py | 1545 | Profil: `_verify_password()` für altes Passwort |
| basis14.py | 1554 | Profil: `_hash_password()` für neues Passwort (bcrypt) |
## Migrationslogik
```
Login-Versuch:
1. _verify_password(pw, stored_hash) aufrufen
2. Prüfe: beginnt stored_hash mit "$2b$" oder "$2a$"?
JA → bcrypt.checkpw()
NEIN → SHA-256 Legacy-Vergleich
3. Bei erfolgreicher Anmeldung:
_is_legacy_hash(stored_hash)?
JA → Rehash mit bcrypt, Profil speichern
NEIN → Nichts tun
```
- Kein Forced Reset
- Alter Hash wird beim nächsten Login automatisch ersetzt
- Neuer Hash sofort bei Registration und Passwortänderung
## Legacy-Erkennung
Ein Hash ist Legacy (SHA-256) wenn:
- Er NICHT mit "$2" beginnt (kein bcrypt-Prefix)
- Er exakt 64 Zeichen lang ist (SHA-256 Hex-Output)
## Tests
| Test | Ergebnis |
|------|----------|
| Neuer bcrypt-Hash erstellen | PASS ($2b$12$...) |
| bcrypt verify (korrektes PW) | PASS (True) |
| bcrypt verify (falsches PW) | PASS (False) |
| Legacy SHA-256 erkennen | PASS (is_legacy=True) |
| bcrypt als nicht-Legacy erkennen | PASS (is_legacy=False) |
| Legacy verify (korrektes PW) | PASS (True) |
| Legacy verify (falsches PW) | PASS (False/rejected) |
| Legacy rehash zu bcrypt | PASS (verify nach rehash True) |
## Nicht geändert
- workforce_planner/api/auth.py nutzt bereits bcrypt (cost=default)
- aza_persistence.py importiert hashlib, nutzt es aber nicht für Passwörter
- Backup-Kopien (basis14 - Kopie*.py) bewusst nicht geändert
## Rest-Risiken
1. **bcrypt als Dependency**: Muss installiert sein (`pip install bcrypt`).
Ist bereits Dependency von workforce_planner.
2. **Timing**: Legacy-SHA-256-Vergleich ist schneller als bcrypt.
Ein Timing-Seitenkanalangriff könnte erkennen, ob ein Nutzer
noch einen alten Hash hat. Risiko: minimal (lokale Desktop-App).
3. **workforce_planner cost**: Nutzt bcrypt mit Default-Rounds (10).
Empfehlung: Auf 12 angleichen (separater Schritt).

View File

@@ -0,0 +1,194 @@
# STEP 4.2a HASH-MIGRATION PROOF (AUDIT-NACHWEIS)
# Status: PASS
---
## Test-Setup
- **Datei:** `basis14.py` (Zeilen 12981316)
- **Speicherort:** `kg_diktat_user_profile.json` (JSON, Feld `password_hash`)
- **Testmethode:** Automatisiertes Python-Skript mit Backup/Restore des Original-Profils
- **KDF:** bcrypt, cost=12, Salt eingebettet
- **Legacy:** SHA-256, 64 Hex-Zeichen, kein Salt
- **Testpasswörter:** `AuditTest2026!` (korrekt), `FalschesPasswort` (falsch)
---
## Test 1: Legacy-SHA-256-User anlegen
```
Passwort: AuditTest2026!
SHA-256 Hash: e99f0f4a91440e4a7d89ebd1db548df0eed586dd16e1b3c0e9bb6356220e0f3b
Speicherort: kg_diktat_user_profile.json
```
| Prüfung | Ergebnis |
|---------|----------|
| Profil gespeichert | PASS |
| Hash ist Legacy (64 hex chars) | PASS |
| Hash Länge = 64 | PASS |
| Hash beginnt NICHT mit $2b$ | PASS |
---
## Test 2: Login mit falschem Passwort
```
Passwort: FalschesPasswort
Ergebnis: REJECTED
```
| Prüfung | Ergebnis |
|---------|----------|
| Falsches PW abgelehnt | PASS |
---
## Test 3: Login mit korrektem Passwort + Rehash
### VORHER (Legacy SHA-256):
```
e99f0f4a91440e4a7d89ebd1db548df0eed586dd16e1b3c0e9bb6356220e0f3b
```
### NACHHER (bcrypt):
```
$2b$12$mM1wNUuQJfYMZ23xJRfsJO/JVI0G4ucm9lT.BnTK/LVK8m/gw6CEK
```
| Prüfung | Ergebnis |
|---------|----------|
| Korrektes PW akzeptiert | PASS |
| Hash hat sich geändert | PASS |
| Neuer Hash beginnt mit $2b$ | PASS |
| Neuer Hash ist NICHT Legacy | PASS |
---
## Test 4: Login nach Rehash (bcrypt-Hash)
| Prüfung | Ergebnis |
|---------|----------|
| Korrektes PW nach Rehash | PASS |
| Falsches PW nach Rehash abgelehnt | PASS |
---
## Test 5: Neue Registrierung → direkt bcrypt
```
Neues Passwort: NeuerUser2026!
bcrypt Hash: $2b$12$MdhU4H6CihlPMecPxRGgHuZ0rRJw.QXd1B0tqr4A8xK...
```
| Prüfung | Ergebnis |
|---------|----------|
| Hash ist bcrypt | PASS |
| Hash ist NICHT Legacy | PASS |
| Verify korrekt | PASS |
| Verify falsch abgelehnt | PASS |
---
## Test 6: Passwortänderung → bcrypt
```
Neuer Hash: $2b$12$62I3tJxk09DaPmmu.6SsEeTgSp6UJReaJtSzs57SFO6...
```
| Prüfung | Ergebnis |
|---------|----------|
| Hash ist bcrypt | PASS |
| Altes PW abgelehnt | PASS |
| Neues PW akzeptiert | PASS |
---
## Code-Verifikation
### _hash_password() Zeile 12981301
```python
@staticmethod
def _hash_password(pw: str) -> str:
return bcrypt.hashpw(pw.encode("utf-8"), bcrypt.gensalt(rounds=12)).decode("utf-8")
```
→ Erzeugt bcrypt-Hash mit cost=12 und eingebettetem Salt.
### _verify_password() Zeile 13031309
```python
@staticmethod
def _verify_password(pw: str, stored_hash: str) -> bool:
if stored_hash.startswith("$2b$") or stored_hash.startswith("$2a$"):
return bcrypt.checkpw(pw.encode("utf-8"), stored_hash.encode("utf-8"))
legacy = hashlib.sha256(pw.encode("utf-8")).hexdigest()
return legacy == stored_hash
```
→ Erkennt bcrypt-Hashes am Prefix. Fällt auf SHA-256-Vergleich zurück.
### _is_legacy_hash() Zeile 13111316
```python
@staticmethod
def _is_legacy_hash(stored_hash: str) -> bool:
if not stored_hash:
return False
return not stored_hash.startswith("$2") and len(stored_hash) == 64
```
→ Erkennt alte 64-Zeichen SHA-256-Hashes.
### Login mit Rehash Zeile 13631367
```python
stored = self._user_profile.get("password_hash", "")
if self._verify_password(pw, stored):
if self._is_legacy_hash(stored):
self._user_profile["password_hash"] = self._hash_password(pw)
save_user_profile(self._user_profile)
dlg.destroy()
```
→ Bei erfolgreichem Login: Legacy-Hash automatisch durch bcrypt ersetzt.
### Registrierung Zeile 1465
```python
"password_hash": self._hash_password(pw),
```
→ Nutzt direkt bcrypt.
### Profiländerung (altes PW prüfen) Zeile 1545
```python
if old_hash and not self._verify_password(pw_old_e.get(), old_hash):
```
→ Verifiziert mit _verify_password (bcrypt + Legacy-kompatibel).
### Profiländerung (neues PW setzen) Zeile 1554
```python
pw_hash = self._hash_password(new_pw)
```
→ Nutzt direkt bcrypt.
---
## Gesamtergebnis
| Nr | Testfall | Ergebnis |
|----|----------|----------|
| 1 | Legacy-Hash anlegen & erkennen | PASS |
| 2 | Falsches PW → abgelehnt | PASS |
| 3 | Korrektes PW → akzeptiert + Rehash | PASS |
| 4 | Login nach Rehash funktioniert | PASS |
| 5 | Registrierung schreibt bcrypt | PASS |
| 6 | Passwortänderung schreibt bcrypt | PASS |
**18/18 Einzelprüfungen: PASS**
**0 Fixes notwendig**
**Original-Profil wiederhergestellt**
---
## Bewertung: PASS
Alle Anforderungen erfüllt:
- Legacy SHA-256 wird erkannt
- Login mit korrektem/falschem PW korrekt
- Rehash passiert automatisch beim Login
- Nach Rehash ist der gespeicherte Hash bcrypt
- Neue Registrierungen und Passwortänderungen verwenden direkt bcrypt
- Kein Forced Reset notwendig

View File

@@ -0,0 +1,162 @@
# STEP 4.3 HARDCODED SECRET KEY ENTFERNT (LÜCKE #19)
# Status: ABGESCHLOSSEN
---
## Fundstellen
| Datei | Zeile | Problem |
|-------|-------|---------|
| `workforce_planner/config.py` | 18 | `SECRET_KEY = os.getenv("WP_SECRET_KEY", "dev-secret-change-in-production")` Hardcoded Fallback-Secret |
| `workforce_planner/api/auth.py` | 35, 47 | Konsument: JWT encode/decode mit `SECRET_KEY` |
Keine weiteren hardcoded Secrets gefunden:
- `basis14.py`: OPENAI_API_KEY kommt aus ENV, kein Fallback
- `basis14.py`: MEDWORK_API_TOKEN kommt aus ENV oder Datei, kein hardcoded Wert
- `aza_email_config.json`: Passwörter im Klartext (separates GAP, nicht in diesem Schritt)
---
## Neue ENV-Variablen
| Variable | Pflicht | Beschreibung |
|----------|---------|-------------|
| `AZA_SECRET_KEY` | Ja (ausser DEV) | JWT-Signing-Key, min. 32 Zeichen |
| `AZA_ENV` | Nein | `dev` = erlaubt auto-generierten Key |
Alte Variable `WP_SECRET_KEY` wird **nicht mehr verwendet**.
---
## Implementierung
### workforce_planner/config.py (komplett neu)
**ALT:**
```python
SECRET_KEY = os.getenv("WP_SECRET_KEY", "dev-secret-change-in-production")
```
**NEU:**
```python
def _load_secret_key() -> str:
key = os.getenv("AZA_SECRET_KEY", "").strip()
env_mode = os.getenv("AZA_ENV", "").strip().lower()
# Kein Key: DEV auto-gen oder Fail-Start
if not key:
if env_mode == "dev":
return secrets.token_hex(64) # temporär, nur diese Session
sys.exit(1) # Fehlermeldung + Exit
# Zu kurz (< 32 Zeichen): Fail-Start
if len(key) < 32:
sys.exit(1)
# Triviales Muster: Fail-Start
for pattern in ("dev", "test", "password", "secret", ...):
if key.startswith(pattern) und nicht-alphanumerisches Folgezeichen:
sys.exit(1)
return key
SECRET_KEY = _load_secret_key()
```
### workforce_planner/api/auth.py
Keine Änderung nötig importiert weiterhin `SECRET_KEY` aus `config.py`.
Die Variable hat denselben Namen, der Wert kommt jetzt validiert aus ENV.
---
## Fail-Start Verhalten
| Szenario | Ergebnis |
|----------|----------|
| Kein `AZA_SECRET_KEY`, kein `AZA_ENV=dev` | Exit 1 + Fehlermeldung |
| `AZA_ENV=dev`, kein Key | OK (auto-gen, Warnung auf stderr) |
| Key < 32 Zeichen | Exit 1 + "zu kurz" |
| Key mit trivialem Muster ("dev-...", "test-...", "password...") | Exit 1 + "triviales Muster" |
| Gültiger Key >= 32 Zeichen | OK |
| Nur `WP_SECRET_KEY` gesetzt (alt) | Exit 1 (wird ignoriert) |
---
## Testkommandos + erwartete Outputs
### 1. Fail-Start (kein Key)
```bash
python -c "from workforce_planner.config import SECRET_KEY"
# → Exit 1: FEHLER: AZA_SECRET_KEY ist nicht gesetzt.
```
### 2. DEV-Modus (auto-gen)
```bash
AZA_ENV=dev python -c "from workforce_planner.config import SECRET_KEY; print(len(SECRET_KEY))"
# → 128 (hex-encoded 64 Bytes) + Warnung auf stderr
```
### 3. Zu kurzer Key
```bash
AZA_SECRET_KEY=short123 python -c "from workforce_planner.config import SECRET_KEY"
# → Exit 1: FEHLER: AZA_SECRET_KEY ist zu kurz (8 Zeichen, Minimum: 32)
```
### 4. Triviales Muster
```bash
AZA_SECRET_KEY="test-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" python -c "from workforce_planner.config import SECRET_KEY"
# → Exit 1: FEHLER: AZA_SECRET_KEY enthält triviales Muster ('test')
```
### 5. Gültiger Key
```bash
AZA_SECRET_KEY=$(python -c "import secrets; print(secrets.token_hex(64))") python -c "from workforce_planner.config import SECRET_KEY; print('OK:', len(SECRET_KEY), 'chars')"
# → OK: 128 chars
```
---
## Testergebnis (automatisiert)
```
12/12 PASS, 0/12 FAIL
GESAMTBEWERTUNG: PASS
```
| Test | Ergebnis |
|------|----------|
| Fail-Start ohne Key | PASS |
| Fehlermeldung enthält 'AZA_SECRET_KEY' | PASS |
| DEV auto-gen Key | PASS |
| Key geladen (128 chars) | PASS |
| Warnung ausgegeben | PASS |
| Fail-Start kurzer Key | PASS |
| Fehlermeldung 'zu kurz' | PASS |
| Fail-Start alter Fallback | PASS |
| Fail-Start triviales Pattern (lang) | PASS |
| Start mit gültigem Key | PASS |
| Key geladen (64 chars) | PASS |
| WP_SECRET_KEY allein -> Fail-Start | PASS |
---
## Session/Token-Invalidierung
**Impact:** Bestehende JWT-Tokens (HS256), die mit dem alten Fallback-Key
`"dev-secret-change-in-production"` signiert wurden, werden nach dem
Setzen eines neuen `AZA_SECRET_KEY` **ungültig**.
- Betroffene Nutzer müssen sich **einmalig neu anmelden**.
- Dies betrifft nur den `workforce_planner` (Arbeitsplan-Modul).
- `basis14.py` verwendet keine JWT-Tokens (nur lokales Passwort-Hashing).
- **Keine komplexe Rotation** implementiert (nicht im Scope dieses Schrittes).
- Bei Produktivbetrieb: Key einmalig setzen und dauerhaft beibehalten.
---
## Geänderte Dateien
| Datei | Änderung |
|-------|----------|
| `workforce_planner/config.py` | Hardcoded Fallback entfernt, `_load_secret_key()` mit Validierung + Fail-Start implementiert |

View File

@@ -0,0 +1,166 @@
# STEP 4.4 KLARTEXT-CREDENTIALS ENTFERNT (LÜCKE #28)
# Status: ABGESCHLOSSEN
---
## Fundstellen
| Datei | Problem |
|-------|---------|
| `aza_email_config.json` | Klartext-Passwort im `password`-Feld: `"password": "Asdfasdf22@@As96k324"` |
Keine weiteren Klartext-Credentials in JSON/YAML/INI-Dateien gefunden.
**Hinweis:** Das hier entfernte Passwort war ein echtes Produktivpasswort.
Es sollte umgehend beim Mail-Provider geändert werden.
---
## Neue ENV-Variablen
| Variable | Beschreibung |
|----------|-------------|
| `AZA_EMAIL_PASSWORD_0` | Passwort für das erste E-Mail-Konto |
| `AZA_EMAIL_PASSWORD_1` | Passwort für das zweite E-Mail-Konto (falls vorhanden) |
| `AZA_EMAIL_PASSWORD_N` | Passwort für das N-te Konto (0-basiert) |
### Setzen unter Windows:
```cmd
set AZA_EMAIL_PASSWORD_0=MeinSicheresPasswort
```
### Setzen unter Linux/Mac:
```bash
export AZA_EMAIL_PASSWORD_0="MeinSicheresPasswort"
```
### Dauerhaft (Windows PowerShell):
```powershell
[Environment]::SetEnvironmentVariable("AZA_EMAIL_PASSWORD_0", "MeinPasswort", "User")
```
---
## Geänderte Dateien
| Datei | Änderung |
|-------|----------|
| `aza_email.py` | Neue Funktionen: `_get_account_password()`, `_strip_passwords()`, `_check_plaintext_migration()` |
| `aza_email.py` | `load_email_config()`: Lädt Passwörter aus ENV, ignoriert Klartext in JSON, gibt Warnung |
| `aza_email.py` | `save_email_config()`: Strippt `password` und `_password` vor dem Schreiben |
| `aza_email.py` | Alle IMAP/SMTP-Stellen: `acc.get("password")``acc.get("_password")` |
| `aza_email.py` | Account-Dialog: Passwort wird als `_password` (RAM-only) gespeichert |
| `aza_email_config.json` | Klartext-Passwort entfernt |
---
## Architektur
### Passwort-Fluss (NEU):
```
ENV (AZA_EMAIL_PASSWORD_0)
load_email_config() ──► acc["_password"] (nur RAM)
IMAP/SMTP Login ──► acc.get("_password", "")
save_email_config() ──► _strip_passwords() ──► JSON OHNE Passwort
```
### Passwort-Fluss (GUI-Eingabe):
```
Account-Dialog (Passwort-Feld)
save_account() ──► acc["_password"] = pw_input (nur RAM)
_save_config() ──► save_email_config() ──► _strip_passwords() ──► JSON OHNE Passwort
```
Das `_password`-Feld existiert NUR im RAM. Es wird NIE in JSON geschrieben.
Das `password`-Feld wird beim Laden aus JSON ignoriert und beim Speichern entfernt.
---
## Migrationsverhalten
Wenn `aza_email_config.json` noch ein `password`-Feld enthält:
1. **Warnung auf stderr:**
```
SICHERHEITSWARNUNG: Klartext-Passwoerter in aza_email_config.json gefunden!
Betroffene Konten: andre.surovy@haut-winterthur.ch
Bitte entfernen und stattdessen ENV-Variablen setzen:
AZA_EMAIL_PASSWORD_0
Die Passwoerter in der Datei werden IGNORIERT.
```
2. **Klartext-Passwort wird NICHT übernommen** (kein Auto-Migrate)
3. **Benutzer muss ENV-Variable setzen**, damit E-Mail funktioniert
4. **Beim nächsten Speichern** wird das `password`-Feld automatisch entfernt
---
## Fail-Start Verhalten
| Szenario | Ergebnis |
|----------|----------|
| `AZA_EMAIL_PASSWORD_0` gesetzt | E-Mail funktioniert |
| `AZA_EMAIL_PASSWORD_0` nicht gesetzt | E-Mail-Abruf zeigt: "Konto-Daten unvollständig" |
| Klartext-PW in JSON | Warnung + wird ignoriert |
| GUI-Passworteingabe | Funktioniert für Sitzung (nicht persistent) |
---
## Testergebnis
```
12/12 PASS, 0/12 FAIL
GESAMTBEWERTUNG: PASS
```
| Test | Ergebnis |
|------|----------|
| Kein Passwort in JSON | PASS |
| Warnung auf stderr bei Klartext | PASS |
| Passwort NICHT in geladenen Daten | PASS |
| ENV-PW in _password Feld | PASS |
| Kein password Feld nach Laden | PASS |
| Kein password in gespeicherter Datei | PASS |
| Kein _password in gespeicherter Datei | PASS |
| password entfernt (Konto 1) | PASS |
| password entfernt (Konto 2) | PASS |
| _password entfernt (nie auf Disk) | PASS |
| Andere Felder erhalten | PASS |
| Kein print mit password-Wert | PASS |
---
## Rest-Risiken
1. **Passwort im RAM:** Während der Sitzung liegt das Passwort im RAM
(Python-String). Für eine Desktop-App akzeptabel.
2. **ENV-Variable sichtbar:** `AZA_EMAIL_PASSWORD_0` kann in Prozesslisten
sichtbar sein. Empfehlung für Zukunft: OS-Keychain-Integration
(Windows Credential Manager / macOS Keychain).
3. **Altes Passwort exponiert:** Das Passwort `Asdfasdf22@@As96k324` war
in der JSON-Datei gespeichert. Es sollte beim Provider geändert werden.
4. **Backup-Dateien:** Die Kopie-Dateien (`aza_email - Kopie.py`, etc.)
enthalten noch den alten Code. Diese sind Backups und nicht aktiv.
---
## Repo-Hygiene
- Kein Git-Repository vorhanden → kein `.gitignore`-Eintrag nötig
- Kein History-Rewrite nötig
- `aza_email_config.json` wurde direkt bereinigt (Passwort entfernt)

View File

@@ -0,0 +1,216 @@
# STEP 6 TOTP-ZWEI-FAKTOR-AUTHENTIFIZIERUNG
# Status: ABGESCHLOSSEN
---
## Architektur
### Komponenten
```
aza_totp.py Zentrales TOTP-Modul (RFC 6238)
basis14.py GUI-Integration (Login, Profil, Setup)
pyotp (2.9.0) TOTP-Implementierung (RFC 6238 kompatibel)
qrcode[pil] (8.2) QR-Code-Generierung
```
### Flow
```
Login-Dialog
Passwort prüfen ──► FAIL → Fehlermeldung
▼ OK
2FA aktiv? ──► NEIN + 2FA_REQUIRED → Erzwinge 2FA-Setup
│ │
▼ JA ▼
TOTP-Dialog ──────────────────────── 2FA-Setup-Dialog
│ │
▼ ▼
Code prüfen QR-Code scannen
│ Code validieren
├─ TOTP OK → Login │
├─ Backup-Code OK → Login Backup-Codes anzeigen
└─ FAIL → Fehlermeldung │
2FA aktiviert
```
---
## Datenmodell
### Profil (kg_diktat_user_profile.json)
```json
{
"name": "Dr. Beispiel",
"specialty": "Dermatologie",
"clinic": "Praxis XY",
"password_hash": "$2b$12$...",
"totp_active": true,
"totp_secret_enc": "Base64-verschlüsselter-TOTP-Secret",
"backup_codes": [
"sha256-hash-code-1",
"sha256-hash-code-2",
"",
"sha256-hash-code-4",
...
]
}
```
| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `totp_active` | boolean | 2FA aktiviert/deaktiviert |
| `totp_secret_enc` | string | TOTP-Secret, XOR-verschlüsselt mit Passwort-Hash, Base64 |
| `backup_codes` | string[] | SHA-256-Hashes der Backup-Codes; leer = verbraucht |
---
## TOTP-Parameter
| Parameter | Wert |
|-----------|------|
| Algorithmus | TOTP (RFC 6238) |
| Hash | SHA-1 (Standard per RFC) |
| Zeitschritt | 30 Sekunden |
| Code-Länge | 6 Ziffern |
| Gültigkeitsfenster | +-1 (90 Sekunden total) |
| Secret-Länge | 160 bit (32 Base32-Zeichen) |
---
## Sicherheitsmassnahmen
### Secret-Verschlüsselung
- TOTP-Secret wird NIE im Klartext gespeichert
- Verschlüsselung: XOR mit SHA-256(Benutzer-Passwort)
- Encoding: Base64
- Entschlüsselung nur mit korrektem Passwort möglich
- Bei falschem Passwort: leerer String (kein Crash)
### Backup-Codes
- 8 Einmal-Codes (8 Zeichen, hex, uppercase)
- Gespeichert als SHA-256-Hashes
- Verbrauchte Codes werden zu leerem String
- Case-insensitive Eingabe
- HMAC-basierter Vergleich (timing-safe)
### Rate-Limiting
- Max. 5 TOTP-Versuche pro 5 Minuten pro Benutzer
- Nach Erreichen: auch korrekter Code wird blockiert
- Im RAM gehalten (Reset bei Neustart)
---
## ENV-Variablen
| Variable | Default | Beschreibung |
|----------|---------|-------------|
| `AZA_2FA_ENABLED` | `1` | 2FA-Feature verfügbar |
| `AZA_2FA_REQUIRED` | `0` | 2FA ist Pflicht für alle Benutzer |
### Verhalten
| AZA_2FA_ENABLED | AZA_2FA_REQUIRED | Ergebnis |
|:---:|:---:|---|
| 0 | 0 | 2FA komplett deaktiviert, kein Button im Profil |
| 1 | 0 | 2FA optional, Aktivierung im Profil |
| 1 | 1 | 2FA Pflicht, Setup nach erstem Login erzwungen |
| 0 | 1 | Feature deaktiviert, Required ignoriert |
---
## Recovery
- Wiederherstellung NUR über Backup-Codes
- Kein Admin-Reset ohne Prüfung
- Kein E-Mail-Recovery
- Backup-Codes werden nach 2FA-Aktivierung einmalig angezeigt
- Benutzer muss Codes selbst sichern (Kopieren/Ausdrucken)
- Bei aufgebrauchten Backup-Codes: Profil-Datei manuell bereinigen (Admin-Eingriff)
---
## Geänderte / Neue Dateien
| Datei | Änderung |
|-------|----------|
| `aza_totp.py` | NEU: Zentrales TOTP-Modul |
| `basis14.py` | Import: pyotp, qrcode, PIL.ImageTk, aza_totp |
| `basis14.py` | `_show_password_login()`: 2FA-Check nach Passwort |
| `basis14.py` | `_show_totp_login()`: NEU TOTP-Code-Dialog |
| `basis14.py` | `_show_2fa_setup()`: NEU QR-Code + Erst-Validierung |
| `basis14.py` | `_show_backup_codes()`: NEU Backup-Code-Anzeige |
| `basis14.py` | `_show_profile_editor()`: 2FA-Aktivierung/Deaktivierung |
---
## Dependencies
```
pyotp==2.9.0 # TOTP RFC 6238
qrcode[pil]==8.2 # QR-Code-Generierung
```
---
## Testergebnis
```
24/24 PASS, 0/24 FAIL
GESAMTBEWERTUNG: PASS
```
| Test | Ergebnis |
|------|----------|
| Secret-Generierung (Base32, 32 Zeichen) | PASS |
| Secrets sind einzigartig | PASS |
| Provisioning URI (otpauth://) | PASS |
| URI enthält Issuer | PASS |
| URI enthält Secret | PASS |
| Korrekter Code akzeptiert | PASS |
| Falscher Code abgelehnt | PASS |
| Leerer Code abgelehnt | PASS |
| Abgelaufener Code (90s) abgelehnt | PASS |
| 8 Backup-Codes generiert | PASS |
| Code-Format (8 hex chars) | PASS |
| Codes einzigartig | PASS |
| Hashes SHA-256 | PASS |
| Backup-Code Lookup | PASS |
| Falscher Backup-Code abgelehnt | PASS |
| Case-insensitive | PASS |
| Einmalnutzung | PASS |
| Invalidierter Code abgelehnt | PASS |
| Secret-Verschlüsselung | PASS |
| Verschlüsselt != Klartext | PASS |
| Entschlüsselung korrekt | PASS |
| Falsches PW = falsches Secret | PASS |
| Rate-Limit greift nach 5 Versuchen | PASS |
| Rate-Limit blockiert auch korrekte Codes | PASS |
---
## Risiken
1. **XOR-Verschlüsselung:** Für eine Desktop-App akzeptabel, aber kryptographisch
schwächer als AES. Upgrade auf Fernet/AES bei Bedarf.
2. **Secret an Passwort gebunden:** Bei Passwortänderung muss das TOTP-Secret
neu verschlüsselt werden. Aktuell nicht automatisch 2FA muss nach
Passwortänderung neu eingerichtet werden.
3. **Rate-Limit im RAM:** Wird bei Neustart zurückgesetzt. Für Desktop-App
akzeptabel (kein Netzwerk-Angriff möglich).
4. **Kein TOTP für workforce_planner:** Die 2FA ist nur im Desktop-Client
(basis14.py) implementiert. Der workforce_planner (Web-API) hat noch
keine 2FA-Integration.
5. **SHA-1 in TOTP:** RFC 6238 spezifiziert SHA-1 als Standard. Alle gängigen
Authenticator-Apps (Google, Microsoft, Authy) erwarten SHA-1.

View File

@@ -0,0 +1,59 @@
# STEP 8 Datenschutzerklärung & KI-Einwilligung
## Ziel
Erstellung rechtlich belastbarer Texte für DSG (Schweiz), DSGVO (EU),
medizinische Daten und KI-Verarbeitung.
## Erstellte Dateien
### /legal/privacy_policy.md
Vollständige Datenschutzerklärung mit:
- Verantwortlicher (Platzhalter für Praxisdaten)
- 4 Verarbeitungszwecke (Dokumentation, Kommunikation, Organisation, KI)
- Datenarten inkl. expliziter Nennung von Gesundheitsdaten (Art. 5 DSG)
- Rechtsgrundlagen DSG + DSGVO
- Speicherorte (lokal + Supabase + OpenAI API)
- Auftragsverarbeiter: OpenAI (USA, SCCs), Supabase (EU-Hosting, USA-Sitz)
- Drittlandübertragung dokumentiert
- Betroffenenrechte DSG + DSGVO
- Beschwerderecht (EDÖB + EU-Aufsicht)
- Kontaktstelle
### /legal/ai_consent.md
KI-Einwilligungserklärung mit:
- Erklärung aller 4 KI-Einsatzgebiete (Transkription, KG, Interaktion, Textprüfung)
- Konkrete Angabe der übermittelten Daten pro Funktion
- Benennung des Anbieters (OpenAI) und der Modelle
- Drittlandhinweis (USA, SCCs)
- Klare Abgrenzung: Was KI NICHT entscheidet (keine Diagnosen, keine Therapie)
- Ärztliche Verantwortung explizit festgehalten
- Widerrufsrecht mit konkreten Folgen
- Dokumentationspflicht
- Unterschriftenfelder (Einwilligung + Widerruf)
## Betroffene Dateien
| Datei | Aktion |
|-------|--------|
| /legal/privacy_policy.md | NEU erstellt |
| /legal/ai_consent.md | NEU erstellt |
## Datengrundlage
Alle Aussagen basieren auf dem tatsächlichen Code-Stand:
- OpenAI API-Nutzung: Transkription (gpt-4o-mini-transcribe), Text (gpt-5.2/5-mini/5-nano)
- Supabase Cloud-Sync (eu-central-1)
- Lokale Datenspeicherung (JSON, SQLite)
- Implementierte Sicherheitsmassnahmen aus STEP 4.x und 6
## Offene Punkte
1. **Verantwortlicher:** Platzhalter muss mit echten Praxisdaten ausgefüllt werden
2. **Juristische Prüfung:** Beide Dokumente müssen vor Produktiveinsatz von
einer rechtskundigen Person geprüft werden
3. **AV-Vertrag OpenAI:** DPA/AVV mit OpenAI muss geprüft/abgeschlossen werden
4. **AV-Vertrag Supabase:** DPA/AVV mit Supabase muss geprüft/abgeschlossen werden
5. **Einwilligungsformular:** Muss in druckbare Form gebracht werden (PDF/Papier)
6. **Kantonale Besonderheiten:** Aufbewahrungsfristen variieren je nach Kanton
## Risiken
- Ohne juristische Prüfung sind die Texte nicht rechtsverbindlich
- AV-Verträge mit OpenAI und Supabase fehlen noch (RED im Audit)
- Drittlandübertragung USA ist datenschutzrechtlich sensibel

View File

@@ -0,0 +1,233 @@
# STEP 9 Backup, Recovery & Löschkonzept
## Ziel
Sicherstellung von Datenverlustminimierung, Wiederherstellbarkeit
und Erfüllung der DSG-Löschpflichten.
---
## 1. Datenübersicht
### 1.1 Medizinische / Personenbezogene Daten
| Datei / Speicher | Format | Inhalt | Sensibilität |
|---|---|---|---|
| `kg_diktat_user_profile.json` | JSON | Arztname, Fachgebiet, Passwort-Hash, TOTP-Secret | HOCH |
| `kg_diktat_ablage/KG/` | Dateien | Krankengeschichten | HOCH |
| `kg_diktat_ablage/Briefe/` | Dateien | Arztbriefe | HOCH |
| `kg_diktat_ablage/Rezepte/` | Dateien | Rezepte | HOCH |
| `kg_diktat_ablage/Kostengutsprachen/` | Dateien | Kostengutsprachen | HOCH |
| `kg_diktat_ablage/Diktat/` | Dateien | Diktattexte | HOCH |
| `kg_diktat_notes.json` | JSON | Notizen (ev. Patientenbezug) | MITTEL |
| `kg_diktat_todos.json` | JSON | Aufgaben (ev. Patientenbezug) | MITTEL |
| `kg_diktat_todo_inbox.json` | JSON | Eingehende Aufgaben | MITTEL |
| `kg_diktat_medwork_contacts.json` | JSON | Praxiskontakte | MITTEL |
| `aza_email_contacts.json` | JSON | E-Mail-Kontakte | MITTEL |
| `aza_medwork_messages.json` | JSON | Nachrichten | MITTEL |
| `workforce_planner.db` | SQLite | Mitarbeiter, Abwesenheiten, Audit-Log | MITTEL |
### 1.2 Konfigurationsdaten (nicht personenbezogen)
| Datei | Inhalt |
|---|---|
| `kg_diktat_config.txt` | Grundeinstellungen |
| `kg_diktat_signature.txt` | Arzt-Signatur |
| `kg_diktat_korrekturen.json` | Auto-Korrekturen |
| `kg_diktat_textbloecke.json` | Textbausteine |
| `kg_diktat_autotext.json` | Autotext-Vorlagen |
| `kg_diktat_soap_presets.json` | SOAP-Profile |
| `kg_diktat_brief_presets.json` | Brief-Profile |
| `aza_email_config.json` | E-Mail-Konfiguration (ohne Passwort) |
| Diverse `*_window.txt` | Fensterpositionen / UI-State |
### 1.3 Cloud-Daten
| Dienst | Daten | Speicherort |
|---|---|---|
| Supabase | Synchronisierte Praxisdaten | AWS eu-central-1 |
| OpenAI API | Keine persistente Speicherung (API-Modus, max. 30 Tage) | USA |
---
## 2. Backup-Konzept
### 2.1 Implementierung
Neues Skript: **`aza_backup.py`**
```
python aza_backup.py backup # Backup erstellen
python aza_backup.py list # Backups auflisten
python aza_backup.py verify <f> # Integrität prüfen
python aza_backup.py restore <f> # Wiederherstellen
python aza_backup.py cleanup # Alte Backups entfernen
```
### 2.2 Was wird gesichert
- Alle medizinischen JSON-Dateien (17 Dateien)
- Alle Konfigurationsdateien (12 Dateien)
- Alle UI-State-Dateien (11 Dateien)
- Komplettes Ablage-Verzeichnis (KG, Briefe, Rezepte, etc.)
- Lernmodus-Export
- Workforce-Planner-Datenbank (SQLite)
### 2.3 Backup-Parameter
| Parameter | Wert | Konfiguration |
|---|---|---|
| Format | ZIP (Deflate, Level 9) | Fest |
| Intervall | Täglich empfohlen | Manuell oder Cron/Task Scheduler |
| Aufbewahrung | 90 Tage (Standard) | `AZA_BACKUP_KEEP_DAYS` |
| Zielverzeichnis | `./backups/` | `AZA_BACKUP_DIR` |
| Integrität | SHA-256 pro Datei + Manifest | Automatisch |
| Benennung | `aza_backup_YYYY-MM-DD_HH-MM-SS.zip` | Automatisch |
### 2.4 Verschlüsselung
- **Transport:** ZIP-Dateien können auf verschlüsseltem Medium gespeichert werden
- **Empfehlung:** Backup-Zielverzeichnis auf BitLocker-verschlüsseltem
Laufwerk oder verschlüsseltem NAS ablegen
- **ZIP-eigene Verschlüsselung:** Nicht implementiert (Python-zipfile
unterstützt kein starkes AES; Laufwerksverschlüsselung wird empfohlen)
### 2.5 Offsite-Kopie
- Backup-Verzeichnis (`AZA_BACKUP_DIR`) kann auf externes Medium
oder Netzlaufwerk zeigen
- Empfohlen: Regelmässige Kopie auf externes verschlüsseltes Medium
- Keine automatische Cloud-Sicherung implementiert
### 2.6 Automatisierung (Windows Task Scheduler)
```
schtasks /create /tn "AZA_Backup" /tr "python C:\...\aza_backup.py backup" /sc daily /st 22:00
```
---
## 3. Recovery-Konzept
### 3.1 Recovery Point Objective (RPO)
| Szenario | RPO |
|---|---|
| Tägliches Backup | Max. 24 Stunden Datenverlust |
| Stündliches Backup (optional) | Max. 1 Stunde |
### 3.2 Recovery Time Objective (RTO)
| Szenario | RTO |
|---|---|
| Einzelne Datei | < 5 Minuten |
| Komplettes System | < 30 Minuten |
### 3.3 Restore-Anleitung
#### Schritt 1: Verfügbare Backups anzeigen
```
python aza_backup.py list
```
#### Schritt 2: Backup-Integrität prüfen
```
python aza_backup.py verify backups/aza_backup_YYYY-MM-DD_HH-MM-SS.zip
```
#### Schritt 3: Dry-Run (was wird wiederhergestellt?)
```
python aza_backup.py restore backups/aza_backup_YYYY-MM-DD_HH-MM-SS.zip --dry-run
```
#### Schritt 4: Tatsächliche Wiederherstellung
```
python aza_backup.py restore backups/aza_backup_YYYY-MM-DD_HH-MM-SS.zip
```
**Sicherheitsmechanismus:** Vor dem Restore wird automatisch eine
Pre-Restore-Sicherung der aktuellen Daten erstellt (in `backups/.pre_restore_*/`),
sodass der Restore rückgängig gemacht werden kann.
### 3.4 Test-Ergebnisse (22.02.2026)
| Test | Ergebnis |
|---|---|
| Backup erstellen | PASS 44 Dateien, 0.06 MB |
| Backup verifizieren (SHA-256) | PASS alle Checksummen korrekt |
| Restore Dry-Run | PASS 44 Dateien korrekt aufgelistet |
| Backup auflisten | PASS Datum, Grösse, Name korrekt |
| Löschung Dry-Run | PASS Warnung zu Backups korrekt |
---
## 4. Löschkonzept
### 4.1 Löschfristen
| Datenkategorie | Aufbewahrungsfrist | Rechtsgrundlage |
|---|---|---|
| Krankengeschichten | 1020 Jahre (kantonal) | Kantonale Gesundheitsgesetze |
| Arztbriefe | 1020 Jahre | Kantonale Gesundheitsgesetze |
| Rezepte | 10 Jahre | OR Art. 958f |
| Mitarbeiterdaten | Anstellungsdauer + 5 Jahre | OR Art. 128 |
| E-Mail-Kontakte | Bis Löschung durch Benutzer | DSG Art. 6 |
| Backups | 90 Tage (konfigurierbar) | Intern |
### 4.2 Technische Umsetzung
#### Automatische Backup-Bereinigung
```
python aza_backup.py cleanup
```
Entfernt Backups älter als `AZA_BACKUP_KEEP_DAYS` (Standard: 90 Tage).
#### Patientendaten löschen (Recht auf Vergessenwerden)
```
# Dry-Run (nur prüfen, was gelöscht wird)
python aza_backup.py delete-patient "Nachname Vorname"
# Tatsächliche Löschung
python aza_backup.py delete-patient "Nachname Vorname" --execute
```
### 4.3 Lösch-Workflow
1. **Antrag:** Patient stellt Löschanfrage (schriftlich empfohlen)
2. **Prüfung:** Arzt prüft, ob gesetzliche Aufbewahrungspflicht besteht
3. **Dry-Run:** `delete-patient --dry-run` zeigt betroffene Dateien
4. **Dokumentation:** Löschanfrage und Ergebnis im Audit-Log festhalten
5. **Ausführung:** `delete-patient --execute`
6. **Backups:** Bestehende Backups enthalten noch Daten nach Ablauf
der Aufbewahrungsfrist (90 Tage) werden diese automatisch entfernt
7. **Cloud:** Supabase-Daten manuell löschen (aktuell kein API-Endpunkt)
### 4.4 Einschränkungen
- Löschung in bestehenden Backups nur durch Warten auf Ablauf
oder manuelles Löschen der betroffenen Backups
- Supabase-Cloud-Daten müssen manuell gelöscht werden
- OpenAI-API-Daten werden gemäss OpenAI nach max. 30 Tagen gelöscht
- Namensbasierte Suche funktioniert nur bei Dateinamen / JSON-Inhalt
mit Patientenname
---
## 5. Geänderte / Neue Dateien
| Datei | Aktion |
|---|---|
| `aza_backup.py` | NEU Backup, Verify, Restore, Cleanup, Löschung |
| `backups/` | NEU Backup-Verzeichnis (automatisch erstellt) |
---
## 6. Risiken
| Risiko | Bewertung | Massnahme |
|---|---|---|
| Kein automatischer Backup-Schedule | MITTEL | Task Scheduler einrichten |
| ZIP nicht AES-verschlüsselt | MITTEL | Laufwerksverschlüsselung nutzen |
| Keine Offsite-Kopie konfiguriert | HOCH | `AZA_BACKUP_DIR` auf NAS setzen |
| Supabase-Löschung manuell | MITTEL | API-Endpunkt implementieren |
| Namensbasierte Löschung unvollständig | NIEDRIG | Erweiterte Suche implementieren |
| Kein inkrementelles Backup | NIEDRIG | Bei Datenwachstum nachrüsten |