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
- Sicherheitsmodell
- Zero-Knowledge-Architektur â NEU (Phase 45)
- Multi-Tenancy-Sicherheit (Phase 42)
- VerschlÃŒsselungsdetails
- SchlÃŒsselverwaltung
- AngriffsflÀche
- Compliance
- Audit-Historie
Sicherheitsmodell
Aktueller Zustand (Phase 48)
Authentifizierung & Autorisierung:
- â JWT (HMAC-SHA256, 24h TTL)
- â OAuth (Google, GitHub)
- â
Multi-Tenancy-Isolation (
owner_id-Gates) - â Path-Traversal-Schutz
- â SSRF-Allowlists
- â
CSP-Header (
default-src 'self',X-Frame-Options: DENY) - â
Sicherheits-Header (
X-Content-Type-Options: nosniff,Referrer-Policy)
Daten im Ruhezustand (Phase 45 â FERTIG â ):
- â
API-Keys verschlÃŒsselt via Vault AES-256-GCM (
encryptField/decryptField) - â Chat-Nachrichten im Ruhezustand in SQLite verschlÃŒsselt (Migration 015, Auto-Encrypt/Decrypt)
- â PII-Bereinigung in JSONL-Logs (E-Mails, API-Keys, JWTs, Kartennummern)
- â
Recovery-Key-Verwaltung (1Password-Stil
XXXX-XXXX-XXXX-XXXX-XXXX)
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:
/api/crm/projects/:name/*(62+ Endpunkte)/api/sse/logs/:name,/api/sse/consultant/:name/ws/terminal/:name/api/cli/*,/api/mcp/*(Knowledge API)
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?
- â Authentifizierte VerschlÃŒsselung (manipulationssicher)
- â Hardware-beschleunigt (AES-NI auf x86)
- â NIST-genehmigt, verwendet von Signal/WhatsApp/TLS 1.3
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:
- Zugriffsaudit-Trail
- SchlÌsselrotations-Richtlinie (jÀhrlich)
- Penetrationstests (vierteljÀhrlich)
- Lieferantenrisikobewertung
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:
- 45.1 â WebCrypto-Fundament (
frontend/src/crm/crypto/e2ee.ts, 214 Zeilen) â - 45.2 â API-Key Vault-VerschlÃŒsselung (
shared/vault.tsencryptField/decryptField) â - 45.3 â Chat-Nachrichten At-Rest-VerschlÃŒsselung (Migration 015, db.ts Auto-Encrypt) â
- 45.4 â Recovery-Keys (Migration 016, 4 API-Endpunkte, RecoveryKeySection-UI) â
- 45.5 â CSP-Header + PII-Bereiniger (
shared/pii-sanitizer.ts) â Urteil: ð¢ Alle P0+P1-Punkte abgeschlossen. P2-Erweiterte-Features (Forward Secrecy, Multi-Device-Sync) verschoben.
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.