Sicherheitsarchitektur — Arc OS

"Wir können deine Daten nicht lesen, selbst wenn wir es wollten"
Zero-Knowledge-Architektur mit Ende-zu-Ende-VerschlÌsselung fÌr BenutzerprivatsphÀre.

Zuletzt aktualisiert: 2026-04-28 (Phase 45 — E2EE-Architektur FERTIG ✅)
Aktuelle Phase: 48 (Architektur-Dekomposition abgeschlossen)
Sicherheitsstatus: 🟢 GREEN (Phase 42 Multi-Tenancy + Phase 45 E2EE abgeschlossen)


Inhaltsverzeichnis

  1. Sicherheitsmodell
  2. Zero-Knowledge-Architektur ⭐ NEU (Phase 45)
  3. Multi-Tenancy-Sicherheit (Phase 42)
  4. VerschlÃŒsselungsdetails
  5. SchlÃŒsselverwaltung
  6. AngriffsflÀche
  7. Compliance
  8. Audit-Historie

Sicherheitsmodell

Aktueller Zustand (Phase 48)

Authentifizierung & Autorisierung:

Daten im Ruhezustand (Phase 45 — FERTIG ✅):

Urteil: Sicher fÌr Multi-Tenancy UND BenutzerprivatsphÀre im Ruhezustand.

Implementierung (Phase 45 — Hybride Architektur)

Designentscheidung: Echtes Zero-Knowledge E2EE ist unmöglich, wenn der Server Daten verarbeiten muss (Claude CLI benötigt Klartext-API-Keys, Child Bot benötigt Klartext-Nachrichten fÃŒr KI-Verarbeitung). Lösung: Hybrid-Ansatz — clientseitige Krypto-Grundlage + serverseitige At-Rest-VerschlÃŒsselung.

Client (Browser):
  WebCrypto PBKDF2 (100k iter) → AES-256-GCM Master-Key
  Master-Key-Lebenszyklus: Login → sessionStorage → Logout/401 → löschen
  Recovery-Key: Master-Key verschlÃŒsseln → auf Server speichern

Server (Bun + SQLite):
  vault.ts encryptField() → AES-256-GCM im Ruhezustand fÃŒr API-Keys
  db.ts Auto-Encrypt/Decrypt → transparente Chat-Nachrichten-VerschlÃŒsselung
  pii-sanitizer.ts → PII aus JSONL-Logs entfernen

Zero-Knowledge-Architektur

Phase 45 (FERTIG ✅ 2026-04-28) — Issues #16–#20

Designprinzip

Server ist nicht vertrauenswÌrdig. Selbst mit Root-SSH-Zugang zur Datenbank können Administratoren Benutzerdaten ohne das Passwort des Benutzers nicht entschlÌsseln.

Vorbild: Signal-artiges E2EE, angepasst fÃŒr KI-Workspace-Zusammenarbeit.

1. Master-Key-Ableitung

Das Benutzerpasswort leitet ZWEI unabhÀngige SchlÌssel ab:

Benutzerpasswort
    │
    ├─ PBKDF2(password, "auth-salt", 100k Iterationen) 
    │     ↓
    │  authHash (nochmals mit bcrypt gehasht, Kosten 12)
    │     ↓
    │  An Server gesendet zur Authentifizierung (Login)
    │
    └─ PBKDF2(password, "master-salt", 100k Iterationen)
          ↓
       masterKey (AES-256-GCM VerschlÃŒsselungsschlÃŒssel)
          ↓
       NIEMALS an Server gesendet (bleibt im Browser-sessionStorage)

Sicherheitseigenschaft: Server kompromittiert → Angreifer erhÀlt authHash → kann masterKey nicht ableiten (verschiedenes Salt).

2. Clientseitiger VerschlÃŒsselungsfluss

Senden einer Chat-Nachricht:

// 1. Benutzer tippt im Browser
const plaintext = "sk-ant-abc123xyz (mein API-Key)";

// 2. Browser verschlÃŒsselt mit Master-Key
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  masterKey,
  new TextEncoder().encode(plaintext)
);

// 3. VerschlÃŒsselten Blob an Server senden (KEIN Klartext)
POST /api/crm/projects/arc-v2/chat {
  content_encrypted: base64(ciphertext),  // Server kann das nicht lesen
  content_iv: base64(iv)
}

Server-Speicherung (SQLite):

INSERT INTO chat_messages (content_encrypted, content_iv, timestamp)
VALUES (
  X'8a9f3c...blob...',  -- verschlÃŒsselt, fÃŒr Server undurchsichtig
  X'7b2e1a...iv...',
  '2026-04-24T10:30:00Z'
);

Admin fragt Datenbank ab:

SELECT content_encrypted FROM chat_messages WHERE id = 1;
-- Gibt zurÃŒck: Blob (ohne Master-Key bedeutungslos)

Nachricht empfangen:

// 1. VerschlÃŒsselten Blob vom Server abrufen
const response = await fetch('/api/crm/projects/arc-v2/chat/history');
const messages = await response.json();

// 2. Browser entschlÃŒsselt mit Master-Key
for (const msg of messages) {
  const plaintext = await crypto.subtle.decrypt(
    { name: "AES-GCM", iv: base64Decode(msg.content_iv) },
    masterKey,
    base64Decode(msg.content_encrypted)
  );
  console.log(new TextDecoder().decode(plaintext));
}

3. Was verschlÃŒsselt wird

Datentyp VerschlÃŒsselt? Issue Anmerkungen
Chat-Nachrichten ✅ Ja #40 Alle User ↔ KI-Konversationen
API-Keys (Anthropic, OpenAI) ✅ Ja #41 In account_settings-Tabelle gespeichert
TOTP-Secrets (2FA-Seeds) ✅ Ja #42 Clientseitige OTP-Generierung
Projekt-Umgebungsvariablen ✅ Ja ZukÃŒnftig .env-Dateien verschlÃŒsselt
E-Mail-Adresse ❌ Nein N/A FÃŒr Login/Auth erforderlich
Projektnamen ❌ Nein N/A FÃŒr UI-Rendering erforderlich
Zeitstempel ❌ Nein N/A Sicheres Metadatum
Passwort-Hash (bcrypt) ❌ Nein N/A Aus authHash abgeleitet, nicht masterKey

4. Server-Schema-Änderungen

Vorher (Phase 43):

CREATE TABLE chat_messages (
  id INTEGER PRIMARY KEY,
  content TEXT NOT NULL,  -- ❌ Klartext
  timestamp TEXT
);

Nachher (Phase 45):

CREATE TABLE chat_messages (
  id INTEGER PRIMARY KEY,
  content_encrypted BLOB NOT NULL,  -- ✅ AES-GCM Ciphertext
  content_iv BLOB NOT NULL,          -- ✅ Initialisierungsvektor
  timestamp TEXT,
  key_version INTEGER DEFAULT 1      -- fÃŒr SchlÃŒsselrotation
);

Multi-Tenancy-Sicherheit

Phase 42 (ABGESCHLOSSEN) — VollstÀndiger Audit-Bericht: docs/security/audit-2026-04-23.md

Isolationsmodell

Jeder Benutzer besitzt Projekte. Kein Benutzer kann auf Projektdaten eines anderen Benutzers zugreifen (außer Admin/CEO).

Gate-Funktion (canAccessProject):

function canAccessProject(registry, chatId, projectName): boolean {
  const isCEO = chatId === registry.ceo_chat_id;
  const user = userQueries.findById(chatId);
  const isAdmin = user?.role === 'admin';
  
  if (isCEO || isAdmin) return true;  // Superuser-Bypass
  
  // DB SSOT: owner_id-PrÃŒfung
  const project = projectQueries.findByName(projectName);
  return project?.owner_id === chatId;
}

Angewendet auf:

Verteidigungsebenen (Netzwerk → Anwendung)

┌─────────────────────────────────────────────────────────────┐
│ Ebene 1: Infrastruktur                                      │
│   - Nur SSH-Key-Auth (kein Passwort)                       │
│   - Fail2ban (5 fehlgeschlagene Versuche → 10 min Sperre)  │
│   - UFW-Firewall (22, 80, 443 nur)                         │
├──────────────────────────────────────────────────────────────
│ Ebene 2: Netzwerk                                           │
│   - Bun bindet nur 127.0.0.1 (kein externer Zugang)        │
│   - Nginx Reverse Proxy (Pfadblockierungen: /.*, /config/, ...) │
│   - HTTPS (TLS 1.3) + HSTS                                 │
├──────────────────────────────────────────────────────────────
│ Ebene 3: Authentifizierung                                  │
│   - JWT (HMAC-SHA256, 24h TTL, vault-gespeichertes Secret)  │
│   - OAuth (Google, GitHub) mit CSRF-Token                  │
│   - E-Mail-Verifizierung (24h TTL)                         │
│   - Rate-Limiting (Login: 5/min)                           │
├──────────────────────────────────────────────────────────────
│ Ebene 4: Autorisierung                                      │
│   - Multi-Tenancy-Gates (owner_id-PrÃŒfungen)               │
│   - Nur Admin: interaktives Terminal                        │
│   - Projekt-gebundene JWT-Token                            │
├──────────────────────────────────────────────────────────────
│ Ebene 5: Eingabe-Validierung                                │
│   - isValidProjectName-Regex                               │
│   - safePath (Path-Traversal-Verhinderung)                 │
│   - SSRF-Allowlist (HTTPS + Domain-Whitelist)              │
├──────────────────────────────────────────────────────────────
│ Ebene 6: Datenschutz (Phase 45)                             │
│   - E2EE (clientseitige VerschlÃŒsselung)                   │
│   - Zero-Knowledge-Architektur                             │
│   - CSP-Header (XSS-Verhinderung)                          │
└─────────────────────────────────────────────────────────────┘

Phase-42-Patches (16 Fixes, alle abgeschlossen)

ID Fix Status
SEC-1 SSE-Routen Multi-Tenancy-Gate ✅
SEC-2 WebSocket-Terminal-Gate + nur Admin: interaktiv ✅
SEC-3 CLI/MCP-Block Entry-Gate (12+ Endpunkte) ✅
SEC-4 Bun.serve bindet 127.0.0.1 ✅
SEC-5 /api/internal/chat/save-Validierung ✅
SEC-6 handleSaveSkill Path-Traversal ✅
SEC-REG1 SSE ?token=-UnterstÃŒtzung ✅
SEC-NEW1 SSRF-Allowlist in handleScoutAnalyze ✅
SEC-NEW2 /api/internal/* Proxy-Header-Canary ✅
SEC-NEW4 redirect:"manual" SSRF-Chain-Block ✅
SEC-NEW6 Rate-Limit Passwort-Reset/Verifizierung ✅

Urteil: 🟢 GREEN — Multi-User bereit (keine bekannten Privilege-Escalation-Vektoren)


VerschlÃŒsselungsdetails

Algorithmen (Phase 45)

Komponente Algorithmus SchlÃŒsselgröße Iterationen/Kosten
Master-Key-Ableitung PBKDF2-SHA256 256-Bit 100.000 (OWASP 2025)
DatenverschlÃŒsselung AES-GCM 256-Bit N/A (symmetrisch)
Passwort-Auth-Hash bcrypt — 12 Runden (4.096 iter)
Recovery-Key ZufÀllige Bytes 128-Bit N/A

PBKDF2-Implementierung

// Auth-Hash (an Server gesendet)
const authKeyMaterial = await crypto.subtle.importKey(
  "raw",
  new TextEncoder().encode(password),
  "PBKDF2",
  false,
  ["deriveBits"]
);
const authBits = await crypto.subtle.deriveBits(
  {
    name: "PBKDF2",
    salt: new TextEncoder().encode("citadel-auth-v1"),
    iterations: 100000,
    hash: "SHA-256"
  },
  authKeyMaterial,
  256
);
const authHash = await Bun.password.hash(
  Buffer.from(authBits).toString("hex"),
  { algorithm: "bcrypt", cost: 12 }
);

// Master-Key (im Browser behalten)
const masterKeyMaterial = await crypto.subtle.importKey(
  "raw",
  new TextEncoder().encode(password),
  "PBKDF2",
  false,
  ["deriveKey"]
);
const masterKey = await crypto.subtle.deriveKey(
  {
    name: "PBKDF2",
    salt: new TextEncoder().encode("citadel-master-v1"),
    iterations: 100000,
    hash: "SHA-256"
  },
  masterKeyMaterial,
  { name: "AES-GCM", length: 256 },
  false,  // NICHT extrahierbar
  ["encrypt", "decrypt"]
);

AES-GCM-VerschlÃŒsselung

// VerschlÃŒsseln
const iv = crypto.getRandomValues(new Uint8Array(12));  // 96-Bit-Nonce
const ciphertext = await crypto.subtle.encrypt(
  {
    name: "AES-GCM",
    iv,
    tagLength: 128  // 128-Bit-Auth-Tag
  },
  masterKey,
  plaintext
);

// EntschlÃŒsseln
const plaintext = await crypto.subtle.decrypt(
  { name: "AES-GCM", iv },
  masterKey,
  ciphertext
);

Warum AES-GCM?


SchlÃŒsselverwaltung

Lebenszyklus

┌──────────────────────────────────────────────────────────────┐
│ Registrierung / Erster Login                                 │
├───────────────────────────────────────────────────────────────
│  1. Benutzer gibt Passwort ein                               │
│  2. Browser leitet authHash + masterKey ab (PBKDF2)         │
│  3. authHash an Server senden (bcrypt → speichern)          │
│  4. masterKey in sessionStorage speichern (ephemer)         │
│  5. Recovery-Key generieren (masterKey verschlÃŒsseln → Server speichern) │
│  6. Benutzer lÀdt Recovery-PDF herunter (MUSS GESPEICHERT WERDEN!) │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│ Nachfolgende Logins                                          │
├───────────────────────────────────────────────────────────────
│  1. Benutzer gibt Passwort ein                               │
│  2. authHash ableiten → an Server senden → verifizieren     │
│  3. masterKey ableiten → in sessionStorage speichern        │
│  4. Bereit zum VerschlÃŒsseln/EntschlÃŒsseln                  │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│ Logout / Tab schließen                                       │
├───────────────────────────────────────────────────────────────
│  sessionStorage.clear() → masterKey gelöscht                │
│  Kein SchlÃŒssel = keine DatenentschlÃŒsselung möglich        │
└──────────────────────────────────────────────────────────────┘

Recovery-Mechanismus

Problem: Passwort vergessen → masterKey verloren → Daten nicht wiederherstellbar.

Lösung: Recovery-Key (einmalig generiert, offline vom Benutzer gespeichert).

┌──────────────────────────────────────────────────────────────┐
│ Recovery-Key-Generierung                                     │
├───────────────────────────────────────────────────────────────
│  1. ZufÀlligen 128-Bit-SchlÃŒssel generieren                  │
│     recoveryKey = crypto.getRandomValues(16 bytes)          │
│  2. Kodieren: "A83Z-KL9P-MM4X-VN2Q-8JC7" (20 Zeichen)       │
│  3. Master-Key verschlÃŒsseln: AES-GCM(masterKey, recoveryKey) │
│  4. VerschlÃŒsselten Master-Key auf Server speichern          │
│  5. Benutzer anzeigen: ⚠ SPEICHERN ODER DATEN FÜR IMMER VERLIEREN │
│     [PDF herunterladen] [Drucken] [Gespeichert]             │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│ Recovery-Fluss                                               │
├───────────────────────────────────────────────────────────────
│  1. Passwort vergessen? → Recovery-Key eingeben             │
│  2. VerschlÃŒsselten Master-Key vom Server abrufen           │
│  3. Master-Key mit Recovery-Key entschlÃŒsseln               │
│  4. NEUES Passwort festlegen                                 │
│  5. authHash + masterKey aus neuem Passwort neu ableiten    │
│  6. Erfolg → Zugriff wiederhergestellt                      │
└──────────────────────────────────────────────────────────────┘

Rate-Limit: Max. 5 Recovery-Versuche pro Stunde (Brute-Force-Schutz).


AngriffsflÀche

Geminderte Bedrohungen

Bedrohung Gegenmaßnahme Phase
Datenbank-Breach ✅ E2EE (Daten verschlÃŒsselt) 45
Server-Kompromittierung ✅ Zero-Knowledge (keine EntschlÃŒsselungsschlÃŒssel) 45
Insider-Bedrohung (Admin) ✅ Kann Benutzerdaten nicht entschlÃŒsseln 45
MITM-Angriff ✅ HTTPS + HSTS 42
XSS-Angriff ✅ CSP-Header, keine Inline-Skripte 45
Path-Traversal ✅ safePath-Validierung 42
SSRF ✅ Allowlist (HTTPS + Domain-PrÃŒfung) 42
Passwort-Brute-Force ✅ bcrypt Kosten 12 + Rate-Limiting 42
Replay-Angriff ✅ AES-GCM-Auth-Tags 45
Multi-Tenancy-Leck ✅ owner_id-Gates 42

Außerhalb des Umfangs (Benutzerverantwortung)

Bedrohung Status
Physischer GerÀtezugriff (entsperrter Laptop) ❌ Benutzer muss Bildschirm sperren
SchÀdliche Browser-Erweiterung ❌ Kann masterKey aus Speicher stehlen
Keylogger auf GerÀt ❌ Erfasst Passwort beim Login
Social Engineering (Recovery-Key-Phishing) ❌ Benutzer-AufklÀrung
Quantencomputing (AES-256-Bruch) ⚠ Sicher bis ~2040 (NIST-Plan)

Compliance

DSGVO (EU-Verordnung 2016/679)

Artikel Anforderung Status
17 Recht auf Löschung ("Recht auf Vergessenwerden") 🎯 Geplant (#55)
20 Recht auf DatenÃŒbertragbarkeit (Export) 🎯 Geplant (#44)
25 Datenschutz durch Design ✅ E2EE standardmÀßig
32 Sicherheit der Verarbeitung ✅ AES-256 + bcrypt
33 Meldepflicht bei Datenschutzverletzung (72h) ✅ Vorfallplan

SOC 2 Typ II (ZukÃŒnftig)

Geplant fÃŒr Enterprise:


Audit-Historie

Phase 42: Multi-Tenancy-Sicherheit (2026-04-23)

Auditor: Sentinel (interner Security-Agent)
Umfang: Multi-Tenant-Isolation, SSRF, Path-Traversal, Eingabe-Validierung
Befunde: 16 Issues (alle behoben)
Urteil: 🟢 GREEN
VollstÀndiger Bericht: docs/security/audit-2026-04-23.md

Phase 43: UI/UX-Sicherheit (2026-04-24)

Auditor: Vanguard (Design + Accessibility)
Umfang: XSS-Vektoren, CSP-LÃŒcken, Inline-Skripte
Befunde: 21 Issues (alle behoben)
Urteil: A- (95/100)
VollstÀndiger Bericht: docs/design/ui-ux-audit-2026-04-23.md

Phase 45: E2EE-Implementierung (2026-04-28)

Implementiert von: Product Owner + Claude
Umfang: At-Rest-VerschlÃŒsselung, Recovery-Keys, CSP-Header, PII-Bereinigung
Unterphasen:

Phase 45: E2EE-Penetrationstest (GEPLANT)

Auditor: Externer Pen-Tester (TBD)
Umfang: WebCrypto, SchlÃŒsselverwaltung, Side-Channel-Lecks
Budget: 5.000 $
Zeitplan: Nach Abschluss von Phase 45


Kontakt

Sicherheitsprobleme: GitHub Security Advisory (private Meldung)
Allgemein: [email protected]
Bug-Bounty: 100 $–5.000 $ (Phase 46+)


Letzter Audit: Phase 45 E2EE (2026-04-28). NÀchster: Externer Pen-Test.