refactor: Consolidate standalone services into admin-v2, add new SDK modules

Remove standalone services (ai-compliance-sdk root, developer-portal,
dsms-gateway, dsms-node, night-scheduler) and legacy compliance/dsgvo pages.
Add new SDK pipeline modules (academy, document-crawler, dsb-portal,
incidents, whistleblower, reporting, sso, multi-tenant, industry-templates).
Add drafting engine, legal corpus files (AT/CH/DE), pitch-deck,
blog and Förderantrag pages.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-02-15 09:05:18 +01:00
parent 626f4966e2
commit 70f2b0ae64
396 changed files with 43163 additions and 80397 deletions

View File

@@ -0,0 +1,317 @@
# SDK Protection Middleware
## 1. Worum geht es?
Die SDK Protection Middleware schuetzt die Compliance-SDK-Endpunkte vor einer bestimmten Art von Angriff: der **systematischen Enumeration**. Was bedeutet das?
> *Ein Wettbewerber registriert sich als zahlender Kunde und laesst ein Skript langsam und verteilt alle TOM-Controls, alle Pruefaspekte und alle Assessment-Kriterien abfragen. Aus den Ergebnissen rekonstruiert er die gesamte Compliance-Framework-Logik.*
Der klassische Rate Limiter (100 Requests/Minute) hilft hier nicht, weil ein cleverer Angreifer langsam vorgeht -- vielleicht nur 20 Anfragen pro Minute, dafuer systematisch und ueber Stunden. Die SDK Protection erkennt solche Muster und reagiert darauf.
!!! info "Kern-Designprinzip"
**Normale Nutzer merken nichts.** Ein Lehrer, der im TOM-Modul arbeitet, greift typischerweise auf 3-5 Kategorien zu und wiederholt Anfragen an gleiche Endpunkte. Ein Angreifer durchlaeuft dagegen 40+ Kategorien in alphabetischer Reihenfolge. Genau diesen Unterschied erkennt die Middleware.
---
## 2. Wie funktioniert der Schutz?
Die Middleware nutzt ein **Anomaly-Score-System**. Jeder Benutzer hat einen Score, der bei 0 beginnt. Verschiedene verdaechtige Verhaltensweisen erhoehen den Score. Ueber die Zeit sinkt er wieder ab. Je hoeher der Score, desto staerker wird der Benutzer gebremst.
Man kann es sich wie eine Ampel vorstellen:
| Score | Ampel | Wirkung | Beispiel |
|-------|-------|---------|----------|
| 0-29 | Gruen | Keine Einschraenkung | Normaler Nutzer |
| 30-59 | Gelb | 1-3 Sekunden Verzoegerung | Leicht auffaelliges Muster |
| 60-84 | Orange | 5-10 Sekunden Verzoegerung, reduzierte Details | Deutlich verdaechtiges Verhalten |
| 85+ | Rot | Zugriff blockiert (HTTP 429) | Sehr wahrscheinlich automatisierter Angriff |
### Score-Zerfall
Der Score sinkt automatisch: Alle 5 Minuten wird er mit dem Faktor 0,95 multipliziert. Ein Score von 60 faellt also innerhalb einer Stunde auf etwa 30 -- wenn kein neues verdaechtiges Verhalten hinzukommt.
---
## 3. Was wird erkannt?
Die Middleware erkennt fuenf verschiedene Anomalie-Muster:
### 3.1 Hohe Kategorie-Diversitaet
**Was:** Ein Benutzer greift innerhalb einer Stunde auf mehr als 40 verschiedene SDK-Kategorien zu.
**Warum verdaechtig:** Ein normaler Nutzer arbeitet in der Regel mit 3-10 Kategorien. Wer systematisch alle durchlaeuft, sammelt vermutlich Daten.
**Score-Erhoehung:** +15
```
Normal: tom/access-control → tom/access-control → tom/encryption → tom/encryption
(3 verschiedene Kategorien in einer Stunde)
Verdaechtig: tom/access-control → tom/encryption → tom/pseudonymization → tom/integrity
→ tom/availability → tom/resilience → dsfa/threshold → dsfa/necessity → ...
(40+ verschiedene Kategorien in einer Stunde)
```
### 3.2 Burst-Erkennung
**Was:** Ein Benutzer sendet mehr als 15 Anfragen an die gleiche Kategorie innerhalb von 2 Minuten.
**Warum verdaechtig:** Selbst ein eifriger Nutzer klickt nicht 15-mal pro Minute auf denselben Endpunkt. Das deutet auf automatisiertes Scraping hin.
**Score-Erhoehung:** +20
### 3.3 Sequentielle Enumeration
**Was:** Die letzten 10 aufgerufenen Kategorien sind zu mindestens 70% in alphabetischer oder numerischer Reihenfolge.
**Warum verdaechtig:** Menschen springen zwischen Kategorien -- sie arbeiten thematisch, nicht alphabetisch. Ein Skript dagegen iteriert oft ueber eine sortierte Liste.
**Score-Erhoehung:** +25
```
Verdaechtig: assessment_general → compliance_general → controls_general
→ dsfa_measures → dsfa_necessity → dsfa_residual → dsfa_risks
→ dsfa_threshold → eh_general → namespace_general
(alphabetisch sortiert = Skript-Verhalten)
```
### 3.4 Ungewoehnliche Uhrzeiten
**Was:** Anfragen zwischen 0:00 und 5:00 Uhr UTC.
**Warum verdaechtig:** Lehrer arbeiten tagsüber. Wer um 3 Uhr morgens SDK-Endpunkte abfragt, ist wahrscheinlich ein automatisierter Prozess.
**Score-Erhoehung:** +10
### 3.5 Multi-Tenant-Zugriff
**Was:** Ein Benutzer greift innerhalb einer Stunde auf mehr als 3 verschiedene Mandanten (Tenants) zu.
**Warum verdaechtig:** Ein normaler Nutzer gehoert zu einem Mandanten. Wer mehrere durchprobiert, koennte versuchen, mandantenuebergreifend Daten zu sammeln.
**Score-Erhoehung:** +15
---
## 4. Quota-System (Mengenbegrenzung)
Zusaetzlich zum Anomaly-Score gibt es klassische Mengenbegrenzungen in vier Zeitfenstern:
| Tier | pro Minute | pro Stunde | pro Tag | pro Monat |
|------|-----------|-----------|---------|-----------|
| **Free** | 30 | 500 | 3.000 | 50.000 |
| **Standard** | 60 | 1.500 | 10.000 | 200.000 |
| **Enterprise** | 120 | 5.000 | 50.000 | 1.000.000 |
Wenn ein Limit in irgendeinem Zeitfenster ueberschritten wird, erhaelt der Nutzer sofort HTTP 429 -- unabhaengig vom Anomaly-Score.
---
## 5. Architektur
### Datenfluss eines SDK-Requests
```
Request kommt an
┌─────────────────────────────────────────────────────────────┐
│ Ist der Pfad geschuetzt? │
│ (/api/sdk/*, /api/v1/tom/*, /api/v1/dsfa/*, ...) │
│ Nein → direkt weiterleiten │
└──────────────┬──────────────────────────────────────────────┘
│ Ja
┌─────────────────────────────────────────────────────────────┐
│ User + Tier + Kategorie extrahieren │
│ (aus Session, API-Key oder X-SDK-Tier Header) │
└──────────────┬──────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Multi-Window Quota pruefen │
│ (Minute / Stunde / Tag / Monat) │
│ Ueberschritten → HTTP 429 zurueck │
└──────────────┬──────────────────────────────────────────────┘
│ OK
┌─────────────────────────────────────────────────────────────┐
│ Anomaly-Score laden (aus Valkey) │
│ Zeitbasierten Zerfall anwenden (×0,95 alle 5 min) │
└──────────────┬──────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Anomalie-Detektoren ausfuehren: │
│ ├── Diversity-Tracking (+15 wenn >40 Kategorien/h) │
│ ├── Burst-Detection (+20 wenn >15 gleiche/2min) │
│ ├── Sequential-Enumeration (+25 wenn sortiert) │
│ ├── Unusual-Hours (+10 wenn 0-5 Uhr UTC) │
│ └── Multi-Tenant (+15 wenn >3 Tenants/h) │
└──────────────┬──────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Throttle-Level bestimmen │
│ Level 3 (Score ≥85) → HTTP 429 │
│ Level 2 (Score ≥60) → 5-10s Delay + reduzierte Details │
│ Level 1 (Score ≥30) → 1-3s Delay │
│ Level 0 → keine Einschraenkung │
└──────────────┬──────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Request weiterleiten │
│ Response-Headers setzen: │
│ ├── X-SDK-Quota-Remaining-Minute/Hour │
│ ├── X-SDK-Throttle-Level │
│ ├── X-SDK-Detail-Reduced (bei Level ≥2) │
│ └── X-BP-Trace (HMAC-Watermark) │
└─────────────────────────────────────────────────────────────┘
```
### Valkey-Datenstrukturen
Die Middleware speichert alle Tracking-Daten in Valkey (Redis-Fork). Wenn Valkey nicht erreichbar ist, wird automatisch auf eine In-Memory-Implementierung zurueckgefallen.
| Zweck | Valkey-Typ | Key-Muster | TTL |
|-------|-----------|------------|-----|
| Quota pro Zeitfenster | Sorted Set | `sdk_protect:quota:{user}:{window}` | Fenster + 10s |
| Kategorie-Diversitaet | Set | `sdk_protect:diversity:{user}:{stunde}` | 3660s |
| Burst-Tracking | Sorted Set | `sdk_protect:burst:{user}:{kategorie}` | 130s |
| Sequenz-Tracking | List | `sdk_protect:seq:{user}` | 310s |
| Anomaly-Score | Hash | `sdk_protect:score:{user}` | 86400s |
| Tenant-Tracking | Set | `sdk_protect:tenants:{user}:{stunde}` | 3660s |
### Watermarking
Jede Antwort enthaelt einen `X-BP-Trace` Header mit einem HMAC-basierten Fingerabdruck. Damit kann nachtraeglich nachgewiesen werden, welcher Benutzer wann welche Daten abgerufen hat -- ohne dass der Benutzer den Trace veraendern kann.
---
## 6. Geschuetzte Endpunkte
Die Middleware schuetzt alle Pfade, die SDK- und Compliance-relevante Daten liefern:
| Pfad-Prefix | Bereich |
|-------------|---------|
| `/api/sdk/*` | SDK-Hauptendpunkte |
| `/api/compliance/*` | Compliance-Bewertungen |
| `/api/v1/tom/*` | Technisch-organisatorische Massnahmen |
| `/api/v1/dsfa/*` | Datenschutz-Folgenabschaetzung |
| `/api/v1/vvt/*` | Verarbeitungsverzeichnis |
| `/api/v1/controls/*` | Controls und Massnahmen |
| `/api/v1/assessment/*` | Assessment-Bewertungen |
| `/api/v1/eh/*` | Erwartungshorizonte |
| `/api/v1/namespace/*` | Namespace-Verwaltung |
Nicht geschuetzt sind `/health`, `/metrics` und `/api/health`.
---
## 7. Admin-Verwaltung
Ueber das Admin-Dashboard koennen Anomaly-Scores eingesehen und verwaltet werden:
| Endpoint | Methode | Beschreibung |
|----------|---------|--------------|
| `/api/admin/middleware/sdk-protection/scores` | GET | Aktuelle Anomaly-Scores aller Benutzer |
| `/api/admin/middleware/sdk-protection/stats` | GET | Statistik: Benutzer pro Throttle-Level |
| `/api/admin/middleware/sdk-protection/reset-score/{user_id}` | POST | Score eines Benutzers zuruecksetzen |
| `/api/admin/middleware/sdk-protection/tiers` | GET | Tier-Konfigurationen anzeigen |
| `/api/admin/middleware/sdk-protection/tiers/{name}` | PUT | Tier-Limits aendern |
---
## 8. Dateien und Quellcode
| Datei | Beschreibung |
|-------|--------------|
| `backend/middleware/sdk_protection.py` | Kern-Middleware (~460 Zeilen) |
| `backend/middleware/__init__.py` | Export der Middleware-Klassen |
| `backend/main.py` | Registrierung im FastAPI-Stack |
| `backend/middleware_admin_api.py` | Admin-API-Endpoints |
| `backend/migrations/add_sdk_protection_tables.sql` | Datenbank-Migration |
| `backend/tests/test_middleware.py` | 14 Tests fuer alle Erkennungsmechanismen |
---
## 9. Datenbank-Tabellen
### sdk_anomaly_scores
Speichert Snapshots der Anomaly-Scores fuer Audit und Analyse.
| Spalte | Typ | Beschreibung |
|--------|-----|--------------|
| `id` | UUID | Primaerschluessel |
| `user_id` | VARCHAR(255) | Benutzer-Identifikation |
| `score` | DECIMAL(5,2) | Aktueller Anomaly-Score |
| `throttle_level` | SMALLINT | Aktueller Throttle-Level (0-3) |
| `triggered_rules` | JSONB | Welche Regeln ausgeloest wurden |
| `endpoint_diversity_count` | INT | Anzahl verschiedener Kategorien |
| `request_count_1h` | INT | Anfragen in der letzten Stunde |
| `snapshot_at` | TIMESTAMPTZ | Zeitpunkt des Snapshots |
### sdk_protection_tiers
Konfigurierbare Quota-Tiers, editierbar ueber die Admin-API.
| Spalte | Typ | Beschreibung |
|--------|-----|--------------|
| `tier_name` | VARCHAR(50) | Name des Tiers (free, standard, enterprise) |
| `quota_per_minute` | INT | Maximale Anfragen pro Minute |
| `quota_per_hour` | INT | Maximale Anfragen pro Stunde |
| `quota_per_day` | INT | Maximale Anfragen pro Tag |
| `quota_per_month` | INT | Maximale Anfragen pro Monat |
| `diversity_threshold` | INT | Max verschiedene Kategorien pro Stunde |
| `burst_threshold` | INT | Max gleiche Kategorie in 2 Minuten |
---
## 10. Konfiguration
Die Middleware wird in `main.py` registriert:
```python
from middleware import SDKProtectionMiddleware
app.add_middleware(SDKProtectionMiddleware)
```
Alle Parameter koennen ueber die `SDKProtectionConfig` Dataclass angepasst werden. Die wichtigsten Umgebungsvariablen:
| Variable | Default | Beschreibung |
|----------|---------|--------------|
| `VALKEY_URL` | `redis://localhost:6379` | Verbindung zur Valkey-Instanz |
| `SDK_WATERMARK_SECRET` | (generiert) | HMAC-Secret fuer Watermarks |
---
## 11. Tests
Die Middleware wird durch 14 automatisierte Tests abgedeckt:
```bash
# Alle SDK Protection Tests ausfuehren
docker compose run --rm --no-deps backend \
python -m pytest tests/test_middleware.py -v -k sdk
```
| Test | Prueft |
|------|--------|
| `test_allows_normal_request` | Normaler Request wird durchgelassen |
| `test_blocks_after_quota_exceeded` | 429 bei Quota-Ueberschreitung |
| `test_diversity_tracking_increments_score` | Viele Kategorien erhoehen den Score |
| `test_burst_detection` | Schnelle gleiche Anfragen erhoehen den Score |
| `test_sequential_enumeration_detection` | Alphabetische Muster werden erkannt |
| `test_progressive_throttling_level_1` | Delay bei Score >= 30 |
| `test_progressive_throttling_level_3_blocks` | Block bei Score >= 85 |
| `test_score_decay_over_time` | Score sinkt ueber die Zeit |
| `test_skips_non_protected_paths` | Nicht-SDK-Pfade bleiben frei |
| `test_watermark_header_present` | X-BP-Trace Header vorhanden |
| `test_fallback_to_inmemory` | Funktioniert ohne Valkey |
| `test_no_user_passes_through` | Anonyme Requests passieren |
| `test_category_extraction` | Korrekte Kategorie-Zuordnung |
| `test_quota_headers_present` | Response-Headers vorhanden |

View File

@@ -0,0 +1,456 @@
# Wie funktioniert das Klausur-Namespace-System?
Eine umfassende Erklaerung des BYOEH-Systems -- von der Anonymisierung bis zur sicheren KI-Korrektur.
---
## 1. Was ist das Namespace-System?
Das **BYOEH-System** (Bring Your Own Expectation Horizon) ist eine Datenschutz-Architektur, die es Lehrern ermoeglicht, Klausuren **anonym und verschluesselt** von einer KI korrigieren zu lassen -- ohne dass jemals der Name eines Schuelers den Rechner des Lehrers verlaesst.
> *"Die Klausuren gehen anonym in die Cloud, werden dort von KI korrigiert, und kommen korrigiert zurueck. Nur der Lehrer kann die Ergebnisse wieder den Schuelern zuordnen -- denn nur seine Hardware hat den Schluessel dafuer."*
Das System loest ein grundlegendes Problem: Klausurkorrektur mit KI-Unterstuetzung **ohne Datenschutzrisiko**. Die Loesung besteht aus vier Bausteinen:
1. **Pseudonymisierung:** Namen werden durch zufaellige Codes ersetzt. Niemand ausser dem Lehrer kennt die Zuordnung.
2. **Verschluesselung:** Alles wird *im Browser des Lehrers* verschluesselt, bevor es den Rechner verlaesst. Der Server sieht nur unlesbaren Datensalat.
3. **Namespace-Isolation:** Jeder Lehrer hat einen eigenen, abgeschotteten Bereich (Namespace). Kein Lehrer kann auf die Daten eines anderen zugreifen.
4. **KI-Korrektur:** Die KI arbeitet mit den verschluesselten Daten und dem Erwartungshorizont (EH) des Lehrers. Korrekturvorschlaege gehen zurueck an den Lehrer.
!!! info "Kern-Designprinzip: Operator Blindness"
**Breakpilot kann die Klausuren nicht lesen.** Der Server sieht nur verschluesselte Daten und einen Schluessel-Hash (nicht den Schluessel selbst). Die Passphrase zum Entschluesseln existiert *nur* im Browser des Lehrers und wird niemals uebertragen. Selbst ein Angriff auf den Server wuerde keine Klausurtexte preisgeben.
---
## 2. Der komplette Ablauf im Ueberblick
Der Prozess laesst sich in sieben Schritte unterteilen. Stellen Sie sich vor, der Lehrer sitzt an seinem Rechner und hat einen Stapel gescannter Klausuren:
```text title="Der komplette Workflow: Von der Klausur zur KI-Korrektur"
SCHRITT 1: KLAUSUREN SCANNEN & HOCHLADEN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Lehrer scannt Klausuren ein (PDF oder Bild)
→ System erkennt automatisch den Kopfbereich mit Namen
→ Kopfbereich wird permanent entfernt (Header-Redaction)
→ Jede Klausur erhaelt einen zufaelligen Code (doc_token)
SCHRITT 2: VERSCHLUESSELUNG IM BROWSER
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Lehrer gibt eine Passphrase ein (z.B. "MeinGeheimesPasswort2025!")
→ Browser leitet daraus einen 256-Bit-Schluessel ab (PBKDF2)
→ Klausur wird mit AES-256-GCM verschluesselt
→ Nur der Hash des Schluessels wird an den Server gesendet
→ Passphrase und Schluessel verlassen NIEMALS den Browser
SCHRITT 3: IDENTITAETS-MAP SICHERN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Die Zuordnung "doc_token → Schuelername" wird verschluesselt:
→ Tabelle: "a7f3c2d1... = Max Mustermann, b9e4a1f8... = Anna Schmidt"
→ Diese Tabelle wird mit dem gleichen Schluessel verschluesselt
→ Ohne Passphrase kann niemand die Zuordnung wiederherstellen
SCHRITT 4: UPLOAD IN DEN NAMESPACE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Die verschluesselten Dateien gehen in den persoenlichen Namespace:
→ Jeder Lehrer hat eine eigene tenant_id
→ Daten werden in MinIO (verschluesselt) + Qdrant (Vektoren) gespeichert
→ Server sieht: verschluesselter Blob + Schluessel-Hash + Salt
SCHRITT 5: KI-KORREKTUR
━━━━━━━━━━━━━━━━━━━━━━━
Der Lehrer startet die KI-Korrektur:
→ RAG-System durchsucht den Erwartungshorizont (EH)
→ KI generiert Korrekturvorschlaege pro Kriterium
→ Vorschlaege basieren auf dem EH, nicht auf Halluzinationen
SCHRITT 6: ERGEBNISSE ZURUECK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Korrekturvorschlaege gehen an den Lehrer:
→ Lehrer gibt Passphrase ein
→ Browser entschluesselt die Ergebnisse
→ Lehrer sieht: Vorschlaege pro Kriterium + Gesamtnote
SCHRITT 7: ZUORDNUNG & FINALISIERUNG
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Lehrer ordnet Ergebnisse den Schuelern zu:
→ Identitaets-Map wird entschluesselt
→ doc_token wird wieder dem echten Namen zugeordnet
→ Lehrer ueberprueft/korrigiert KI-Vorschlaege
→ Fertige Korrektur + Gutachten koennen exportiert werden
```
---
## 3. Pseudonymisierung: Wie Namen verschwinden
Pseudonymisierung bedeutet: personenbezogene Daten werden durch **zufaellige Codes** ersetzt, sodass ohne Zusatzinformation kein Rueckschluss auf die Person moeglich ist. Im BYOEH-System passiert das auf zwei Ebenen:
### 3.1 Der doc_token: Ein zufaelliger Ausweis
Jede Klausur erhaelt einen **doc_token** -- einen 128-Bit-Zufallscode im UUID4-Format (z.B. `a7f3c2d1-4e9b-4a5f-8c7d-6b2e1f0a9d3c`). Dieser Code:
- Ist **kryptographisch zufaellig** -- es gibt keinen Zusammenhang zwischen Token und Schueler
- Kann **nicht zurueckgerechnet** werden -- auch mit Kenntnis des Algorithmus ist kein Rueckschluss moeglich
- Wird **auf der Klausur aufgedruckt** (als QR-Code), damit die physische Klausur spaeter wieder zugeordnet werden kann
### 3.2 Header-Redaction: Der Name wird entfernt
Bevor eine Klausur verarbeitet wird, entfernt das System den **Kopfbereich** der gescannten Seite -- dort, wo typischerweise Name, Klasse und Datum stehen. Diese Entfernung ist **permanent**: Die Originaldaten werden nicht gespeichert.
| Methode | Wie es funktioniert | Wann verwendet |
|---------|---------------------|----------------|
| **Einfache Redaction** | Obere ~2,5 cm der Seite werden weiss ueberschrieben | Standard bei allen Uploads |
| **Smarte Redaction** | OpenCV erkennt Textbereiche und entfernt gezielt den Kopf, verschont aber QR-Codes | Wenn QR-Codes auf der Klausur sind |
### 3.3 Die Identitaets-Map: Nur der Lehrer kennt die Zuordnung
Die Zuordnung *doc_token → Schuelername* wird als **verschluesselte Tabelle** gespeichert:
```text title="Datenbank: ExamSession (vereinfacht)"
ExamSession
├── teacher_id = "lehrer-uuid-123" ← Pflichtfeld (Isolation)
├── encrypted_identity_map = [verschluesselte Bytes] ← Nur mit Passphrase lesbar
├── identity_map_iv = "a3f2c1..." ← Initialisierungsvektor (fuer AES)
└── PseudonymizedDocument (pro Klausur)
├── doc_token = "a7f3c2d1-..." ← Zufaelliger Code (Primary Key)
├── exam_session_id = [Referenz]
└── (Kein Name, keine Klasse, kein persoenliches Datum)
```
!!! success "DSGVO Art. 4 Nr. 5 konform"
Die Pseudonymisierung erfuellt die Definition aus der DSGVO: Die personenbezogenen Daten (Schuelernamen) koennen **ohne Hinzuziehung zusaetzlicher Informationen** (der verschluesselten Identitaets-Map + der Passphrase des Lehrers) nicht mehr einer bestimmten Person zugeordnet werden.
---
## 4. Verschluesselung: Wie Daten geschuetzt werden
Die Verschluesselung ist das Herzstueck des Datenschutzes. Sie findet **vollstaendig im Browser** statt -- der Server bekommt nur verschluesselte Daten zu sehen.
### 4.1 Der Verschluesselungsvorgang
Wenn der Lehrer eine Klausur oder einen Erwartungshorizont hochlaedt, passiert im Browser folgendes:
```text title="Client-seitige Verschluesselung (im Browser)"
┌─────────────────────────────────────────────────────────────────┐
│ Browser des Lehrers │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Lehrer gibt Passphrase ein (z.B. "MeinGeheimesPasswort!") │
│ │ ↑ │
│ │ │ Passphrase bleibt hier -- wird NIE gesendet │
│ ▼ │
│ 2. Schluessel-Ableitung: │
│ PBKDF2-SHA256(Passphrase, zufaelliger Salt, 100.000 Runden) │
│ │ │
│ │ → Ergebnis: 256-Bit-Schluessel (32 Bytes) │
│ │ → Selbst bei Kenntnis des Salts sind 100.000 Runden │
│ │ noetig, um den Schluessel zu erraten │
│ ▼ │
│ 3. Verschluesselung: │
│ AES-256-GCM(Schluessel, zufaelliger IV, Datei-Inhalt) │
│ │ │
│ │ → AES-256: Militaerstandard, 2^256 moegliche Schluessel │
│ │ → GCM: Garantiert Integritaet (Manipulation erkennbar) │
│ ▼ │
│ 4. Schluessel-Hash: │
│ SHA-256(abgeleiteter Schluessel) → Hash fuer Verifikation │
│ │ │
│ │ → Der Server speichert nur diesen Hash │
│ │ → Damit kann geprueft werden ob die Passphrase stimmt │
│ │ → Vom Hash kann der Schluessel NICHT zurueckgerechnet │
│ │ werden │
│ ▼ │
│ 5. Upload: Nur diese Daten gehen an den Server: │
│ • Verschluesselter Blob (unlesbar ohne Schluessel) │
│ • Salt (zufaellige Bytes, harmlos) │
│ • IV (Initialisierungsvektor, harmlos) │
│ • Schluessel-Hash (zur Verifikation, nicht umkehrbar) │
│ │
│ Was NICHT an den Server geht: │
│ ✗ Passphrase │
│ ✗ Abgeleiteter Schluessel │
│ ✗ Unverschluesselter Klartext │
└─────────────────────────────────────────────────────────────────┘
```
### 4.2 Warum ist das sicher?
| Angriffsszenario | Was der Angreifer sieht | Ergebnis |
|------------------|-------------------------|----------|
| **Server wird gehackt** | Verschluesselte Blobs + Hashes | Keine lesbaren Klausuren |
| **Datenbank wird geleakt** | encrypted_identity_map (verschluesselt) | Keine Schuelernamen |
| **Netzwerkverkehr abgefangen** | Verschluesselte Daten (HTTPS + AES) | Doppelt verschluesselt |
| **Betreiber will mitlesen** | Verschluesselte Blobs, kein Schluessel | Operator Blindness |
| **Anderer Lehrer versucht Zugriff** | Nichts (Tenant-Isolation) | Namespace blockiert |
---
## 5. Namespace-Isolation: Jeder Lehrer hat seinen eigenen Bereich
Ein **Namespace** (auch "Tenant" genannt) ist ein abgeschotteter Bereich im System. Man kann es sich wie **separate Schliessfaecher** in einer Bank vorstellen: Jeder Lehrer hat sein eigenes Fach, und kein Schluessel passt in ein anderes Fach.
### 5.1 Wie die Isolation funktioniert
Jeder Lehrer erhaelt beim ersten Login eine eindeutige `tenant_id`. Diese ID wird bei **jeder einzelnen Datenbankabfrage** als Pflichtfilter mitgefuehrt:
```text title="Tenant-Isolation in der Vektordatenbank (Qdrant)"
Lehrer A (tenant_id: "school-A-lehrer-1")
├── Klausur 1 (verschluesselt)
├── Klausur 2 (verschluesselt)
└── Erwartungshorizont Deutsch LK 2025
Lehrer B (tenant_id: "school-B-lehrer-2")
├── Klausur 1 (verschluesselt)
└── Erwartungshorizont Mathe GK 2025
Suchanfrage von Lehrer A:
"Wie soll die Einleitung strukturiert sein?"
→ Suche NUR in tenant_id = "school-A-lehrer-1"
→ Lehrer B's Daten sind UNSICHTBAR
Es gibt KEINE Abfrage ohne tenant_id-Filter.
```
### 5.2 Drei Ebenen der Isolation
| Ebene | System | Isolation |
|-------|--------|-----------|
| **Dateisystem** | MinIO (S3-Storage) | Eigener Ordner pro Lehrer: `/tenant-id/eh-id/encrypted.bin` |
| **Vektordatenbank** | Qdrant | Pflichtfilter `tenant_id` bei jeder Suche |
| **Metadaten-DB** | PostgreSQL | Jede Tabelle hat `teacher_id` als Pflichtfeld |
!!! warning "Kein Training mit Lehrerdaten"
Auf allen Vektoren in Qdrant ist das Flag `training_allowed: false` gesetzt. Das bedeutet: Die Inhalte der Lehrer werden **ausschliesslich fuer RAG-Suchen** (Abruf relevanter Textpassagen) verwendet und **niemals zum Trainieren** eines KI-Modells eingesetzt.
---
## 6. Der Erwartungshorizont: Die Grundlage fuer KI-Korrektur
Ein **Erwartungshorizont** (EH) ist das Dokument, das beschreibt, was in einer Klausur erwartet wird: welche Inhalte in welcher Qualitaet vorkommen sollen. Im BYOEH-System laedt der Lehrer seinen eigenen EH hoch, und die KI nutzt ihn als Referenz fuer Korrekturvorschlaege.
### 6.1 Upload-Wizard (5 Schritte)
| Schritt | Was passiert | Warum |
|---------|--------------|-------|
| **1. Datei waehlen** | PDF per Drag & Drop hochladen | Der EH als digitales Dokument |
| **2. Metadaten** | Titel, Fach, Niveau (eA/gA), Jahr | Fuer Filterung und Organisation |
| **3. Rechtebestaetigung** | Checkbox: "Ich bin berechtigt" | Rechtliche Absicherung (Urheberrecht) |
| **4. Verschluesselung** | Passphrase eingeben (2x bestaetigen) | Schluessel fuer Ende-zu-Ende-Verschluesselung |
| **5. Zusammenfassung** | Pruefen und bestaetigen | Letzte Kontrolle vor dem Upload |
### 6.2 RAG-Pipeline: Wie der EH fuer die KI nutzbar wird
Nach dem Upload wird der EH fuer die KI-Suche vorbereitet. Dieser Vorgang heisst **Indexierung** und funktioniert wie das Erstellen eines Stichwortverzeichnisses fuer ein Buch:
```text title="Indexierung: Vom PDF zum durchsuchbaren EH"
Erwartungshorizont (verschluesselt auf Server)
|
v
┌────────────────────────────────┐
│ 1. Passphrase-Verifikation │ ← Lehrer gibt Passphrase ein
│ Hash pruefen │ Server vergleicht mit gespeichertem Hash
└──────────┬─────────────────────┘
|
v
┌────────────────────────────────┐
│ 2. Entschluesselung │ ← Temporaer im Arbeitsspeicher
│ AES-256-GCM Decrypt │ (wird nach Verarbeitung geloescht)
└──────────┬─────────────────────┘
|
v
┌────────────────────────────────┐
│ 3. Text-Extraktion │ ← PDF → Klartext
│ Tabellen, Listen erkennen │
└──────────┬─────────────────────┘
|
v
┌────────────────────────────────┐
│ 4. Chunking │ ← Text in 1.000-Zeichen-Abschnitte zerlegen
│ Ueberlappung: 200 Zeichen │ (mit Ueberlappung fuer Kontext)
└──────────┬─────────────────────┘
|
v
┌────────────────────────────────┐
│ 5. Embedding │ ← Jeder Abschnitt wird in einen
│ Text → 1.536 Zahlen │ Bedeutungsvektor umgewandelt
└──────────┬─────────────────────┘
|
v
┌────────────────────────────────┐
│ 6. Re-Encryption │ ← Jeder Chunk wird ERNEUT verschluesselt
│ AES-256-GCM pro Chunk │ bevor er gespeichert wird
└──────────┬─────────────────────┘
|
v
┌────────────────────────────────┐
│ 7. Qdrant-Indexierung │ ← Vektor + verschluesselter Chunk
│ tenant_id: "lehrer-123" │ werden mit Tenant-Filter gespeichert
│ training_allowed: false │
└────────────────────────────────┘
```
### 6.3 Wie die KI den EH nutzt (RAG-Query)
Wenn der Lehrer bei der Korrektur einen Vorschlag anfordert, passiert folgendes:
1. **Frage formulieren:** Das System erstellt eine Suchanfrage aus dem Klausurtext und dem aktuellen Bewertungskriterium.
2. **Semantische Suche:** Die Anfrage wird in einen Vektor umgewandelt und gegen die EH-Vektoren in Qdrant gesucht -- *nur im Namespace des Lehrers*.
3. **Entschluesselung:** Die gefundenen Chunks werden mit der Passphrase des Lehrers entschluesselt.
4. **KI-Antwort:** Die entschluesselten EH-Passagen werden als Kontext an die KI uebergeben, die daraus einen Korrekturvorschlag generiert.
---
## 7. Key Sharing: Zweitkorrektur ermoeglichen
Bei Abiturklausuren muss eine **Zweitkorrektur** durch einen anderen Lehrer erfolgen. Das Key-Sharing-System ermoeglicht es dem Erstpruefer, seinen Erwartungshorizont sicher mit dem Zweitpruefer zu teilen -- ohne die Verschluesselung aufzugeben.
### 7.1 Einladungs-Workflow
```text title="Key Sharing: Sicheres Teilen zwischen Pruefern"
Erstpruefer Server Zweitpruefer
│ │ │
│ 1. Einladung senden │ │
│ (E-Mail + Rolle + Klausur) │ │
│─────────────────────────────────▶ │
│ │ │
│ │ 2. Einladung erstellt │
│ │ (14 Tage gueltig) │
│ │ │
│ │ 3. Benachrichtigung ──────▶│
│ │ │
│ │ 4. Annehmen
│ │◀─────────────────────────────│
│ │ │
│ │ 5. Key-Share erstellt │
│ │ │
│ │ 6. Zweitpruefer kann ──────▶│
│ │ RAG-Queries ausfuehren │
│ │ │
│ 7. Zugriff widerrufen │ │
│ (jederzeit moeglich) │ │
│─────────────────────────────────▶ │
```
### 7.2 Rollen beim Key-Sharing
| Rolle | Wer | Rechte |
|-------|-----|--------|
| **Erstpruefer (EK)** | Kurslehrer | Vollzugriff, kann teilen & widerrufen |
| **Zweitpruefer (ZK)** | Anderer Fachlehrer | Nur Lesen, RAG-Queries, eigene Annotations |
| **Drittpruefer (DK)** | Bei Differenz ≥ 4 Punkte | Nur Lesen, RAG-Queries |
| **Fachvorsitz** | Fachbereichsleitung | Nur Lesen (Aufsichtsfunktion) |
---
## 8. KI-gestuetzte Bewertung: Wie die Korrektur funktioniert
Die KI bewertet jede Klausur anhand von **fuenf Kriterien**, die zusammen 100% ergeben:
| Kriterium | Gewichtung | Was geprueft wird |
|-----------|------------|-------------------|
| Rechtschreibung | 15% | Orthographie, Zeichensetzung |
| Grammatik | 15% | Satzbau, Kongruenz, Tempus |
| **Inhalt** | **40%** | Bezug zum EH, Vollstaendigkeit, Argumentation |
| Struktur | 15% | Gliederung, Einleitung/Schluss, roter Faden |
| Stil | 15% | Ausdruck, Wortwahl, Fachsprache |
Die Bewertung folgt dem **15-Punkte-System** (0-15 Notenpunkte) der gymnasialen Oberstufe.
!!! info "KI schlaegt vor, Lehrer entscheidet"
Alle KI-Bewertungen sind **Vorschlaege**. Der Lehrer hat bei jedem Kriterium die volle Kontrolle: Er kann den Vorschlag annehmen, aendern oder komplett ueberschreiben. Die finale Note setzt immer der Lehrer.
---
## 9. Audit-Trail: Alles wird protokolliert
Jede Aktion im System wird revisionssicher im **Audit-Log** gespeichert. Das ist wichtig fuer die Nachvollziehbarkeit und fuer den Fall, dass Schueler oder Eltern eine Korrektur anfechten.
| Aktion | Was protokolliert wird |
|--------|------------------------|
| `upload` | EH hochgeladen (Dateigroesse, Metadaten, Zeitstempel) |
| `index` | EH fuer RAG indexiert (Anzahl Chunks, Dauer) |
| `rag_query` | RAG-Suchanfrage ausgefuehrt (Query-Hash, Anzahl Ergebnisse, Score) |
| `share` | EH mit anderem Pruefer geteilt (Empfaenger, Rolle) |
| `revoke_share` | Zugriff widerrufen (wer, wann) |
| `link_klausur` | EH mit Klausur verknuepft |
| `delete` | EH geloescht (Soft Delete, bleibt in Logs) |
---
## 10. API-Endpunkte (Technische Referenz)
Alle Endpunkte laufen ueber den **klausur-service** auf Port 8086.
### 10.1 Erwartungshorizont-Verwaltung
| Methode | Endpunkt | Beschreibung |
|---------|----------|--------------|
| `POST` | `/api/v1/eh/upload` | Verschluesselten EH hochladen |
| `GET` | `/api/v1/eh` | Eigene EHs auflisten |
| `GET` | `/api/v1/eh/{id}` | Einzelnen EH abrufen |
| `DELETE` | `/api/v1/eh/{id}` | EH loeschen (Soft Delete) |
| `POST` | `/api/v1/eh/{id}/index` | EH fuer RAG indexieren |
| `POST` | `/api/v1/eh/rag-query` | RAG-Suchanfrage ausfuehren |
### 10.2 Key Sharing
| Methode | Endpunkt | Beschreibung |
|---------|----------|--------------|
| `POST` | `/api/v1/eh/{id}/share` | EH mit Pruefer teilen |
| `GET` | `/api/v1/eh/{id}/shares` | Geteilte Zugriffe auflisten |
| `DELETE` | `/api/v1/eh/{id}/shares/{shareId}` | Zugriff widerrufen |
| `GET` | `/api/v1/eh/shared-with-me` | Mit mir geteilte EHs |
### 10.3 Klausur-Integration
| Methode | Endpunkt | Beschreibung |
|---------|----------|--------------|
| `POST` | `/api/v1/eh/{id}/link-klausur` | EH mit Klausur verknuepfen |
| `DELETE` | `/api/v1/eh/{id}/link-klausur/{klausurId}` | Verknuepfung loesen |
| `GET` | `/api/v1/klausuren/{id}/linked-eh` | Verknuepften EH abrufen |
---
## 11. Dateistruktur im Code
```text title="Relevante Dateien im Repository"
klausur-service/
├── backend/
│ ├── main.py # API-Endpunkte + Datenmodelle
│ ├── qdrant_service.py # Vektordatenbank-Operationen (Tenant-Filter)
│ └── eh_pipeline.py # Chunking, Embedding, Verschluesselung
├── frontend/
│ └── src/
│ ├── components/
│ │ └── EHUploadWizard.tsx # 5-Schritt-Upload-Wizard
│ └── services/
│ ├── api.ts # API-Client
│ └── encryption.ts # Client-seitige Kryptographie
└── docs/
├── BYOEH-Architecture.md # Technische Architektur
└── BYOEH-Developer-Guide.md # Entwickler-Handbuch
backend/klausur/
├── db_models.py # ExamSession, PseudonymizedDocument
└── services/
└── pseudonymizer.py # QR-Codes, Header-Redaction, doc_tokens
```
---
## 12. Zusammenfassung: Die Sicherheitsgarantien
| Garantie | Wie umgesetzt | Regelwerk |
|----------|---------------|-----------|
| **Kein Name verlaesst den Rechner** | Header-Redaction + verschluesselte Identity-Map | DSGVO Art. 4 Nr. 5 |
| **Betreiber kann nicht mitlesen** | Client-seitige AES-256-GCM Verschluesselung | DSGVO Art. 32 |
| **Kein Zugriff durch andere Lehrer** | Tenant-Isolation (Namespace) auf allen 3 Ebenen | DSGVO Art. 25 |
| **Kein KI-Training mit Schuelerdaten** | `training_allowed: false` auf allen Vektoren | AI Act Art. 10 |
| **Alles nachvollziehbar** | Vollstaendiger Audit-Trail aller Aktionen | DSGVO Art. 5 Abs. 2 |
| **Lehrer behaelt volle Kontrolle** | KI-Vorschlaege, keine KI-Entscheidungen + jederzeitiger Widerruf | DSGVO Art. 22 |
!!! success "Das Wichtigste in einem Satz"
Das BYOEH-System ermoeglicht KI-gestuetzte Klausurkorrektur, bei der **kein Schuelername den Rechner des Lehrers verlaesst**, alle Daten **Ende-zu-Ende verschluesselt** sind, jeder Lehrer seinen **eigenen abgeschotteten Namespace** hat, und die KI **nur Vorschlaege macht** -- die finale Bewertung trifft immer der Lehrer.