# Nexus Board-First Orchestration & Sichere OpenClaw-Integration > Gegenprüfung: Architekt, 2026-06-22 > Gegenstand: Sicherster Pfad für Board-first-Agent-Orchestrierung und MCP-artige/strukturierte > OpenClaw-Integration im Nexus-Backend > Kein Frontend-direkter MCP-Pfad. Kein Deploy. ## 1. Executive Summary ### 1.1 Prüfergebnis Der eingeschlagene Pfad ist **architektonisch korrekt und sicher**. Das Board-first-Modell mit Nexus-Backend als zentraler Brücke zwischen Benutzer, Board und OpenClaw-Gateway ist der richtige Ansatz. Das Backend fungiert bereits als sichere Schicht zwischen allen Akteuren. ### 1.2 Kernbewertung | Aspekt | Status | Bewertung | |--------|--------|-----------| | Board-first Architektur | ✅ Umsetzung läuft | Parent/Child-Modell korrekt implementiert | | Kein Frontend-direkter Gateway-Zugriff | ✅ Eingehalten | Frontend spricht NUR mit Nexus-Backend | | Backend als sichere Brücke | ✅ Eingehalten | API-Container proxyt alle Gateway-Calls | | Auth-/Rechte-Modell | ✅ Solide | JWT + ApiKey + X-Agent-Id Enforcement | | Gateway-Security | ⚠️ Verbesserbar | `loopback`-Bind muss auf `lan` für Docker | | Migration-Reihenfolge | 📋 Vorgeschlagen | (siehe Abschnitt 8) | ## 2. Ist-Architektur: Wer spricht mit wem? ``` ┌──────────────────────────────────────────────────────────────────┐ │ Browser (Bao/Iris) │ │ auth.ts → JWT Access Token (15m) + HttpOnly Refresh Cookie │ │ api.ts → fetch /api/v1/* → Authorization: Bearer │ └───────────────────────────┬──────────────────────────────────────┘ │ HTTPS :443 (Traefik/npm) ▼ ┌──────────────────────────────────────────────────────────────────┐ │ Nexus web (nginx container) │ │ location /api/ → proxy_pass http://api:8080 │ │ location / → SPA (index.html) │ │ CSP: connect-src 'self' — kein externer Gateway-Call möglich │ └───────────────────────────┬──────────────────────────────────────┘ │ HTTP :8080 (internal network) ▼ ┌──────────────────────────────────────────────────────────────────┐ │ Nexus API (.NET 10 Container) ← SICHERE BRÜCKE │ │ │ │ Auth-Middleware (JWT → Controller) │ │ ApiKey-Middleware (X-Nexus-Api-Key → Role=Service) │ │ SecurityHeaders-Middleware (HSTS, CSP, XFO) │ │ Rate Limiter (auth: 5/min/caller, agents: 30/min/caller) │ │ │ │ IOpenClawGatewayClient │ │ ├─ InvokeToolAsync("session_status", ...) │ │ ├─ InvokeToolAsync("sessions_list", ...) │ │ ├─ InvokeToolAsync("sessions_history", ...) │ │ ├─ InvokeToolAsync("memory_search", ...) │ │ ├─ InvokeToolAsync("sessions_send", ...) │ │ └─ InvokeToolAsync("cron", ...) │ │ │ │ GatewayBridgeController (/api/bridge/*) <- NEU (2026-06-22) │ │ └─ ITaskBridgeService │ │ create_task / create_child_task / update_status / │ │ append_activity / handoff / get_board / get_agent_overview │ │ │ │ IAgentRuntime (OpenClawRuntime) │ │ └─ POST /v1/chat/completions (OpenAI-compat) │ │ │ │ ALLE Gateway-Calls → Authorization: Bearer │ └──────────────┬──────────────────────────────┬────────────────────┘ │ openclaw-gateway-bao:18789 │ │ (internes Docker-DNS) │ ▼ │ ┌──────────────────────────────┐ │ │ OpenClaw Gateway Container │ │ │ Port 18789 │ │ │ Auth: password │ │ │ Bind: loopback (127.0.0.1) │ │ │ │ │ │ HTTP Deny-List (default): │ │ │ exec, spawn, shell, │ │ │ fs_write, fs_delete, │ │ │ fs_move, apply_patch, │ │ │ sessions_spawn, sessions_send│ │ │ cron, gateway, nodes, │ │ │ whatsapp_login │ │ └───────────────────────────────┴──────────────┘ ``` ### 2.1 Wichtig: Das Frontend sieht den Gateway NICHT ``` Browser ─── [Nexus API] ─── Gateway │ +─ Gateway-Passwort lebt NUR im Backend +─ CSP: connect-src 'self' blockiert jeden Direktzugriff +─ Gateway HTTP Deny-List blockiert alle RCE-Tools ``` **Das ist die korrekte Architektur.** Kein Frontend-komponenten-direkter MCP-Pfad existiert und keiner sollte eingeführt werden. ## 3. Board-First Orchestrierung: Ist-Stand & Bewertung ### 3.1 Das Parent/Child-Task-Modell (Phase 3) ``` Parent-Task (Owner: Iris) ├── Child-Task A (AssignedTo: programmer) ├── Child-Task B (AssignedTo: reviewer) └── Child-Task C (AssignedTo: architekt) ``` **Status:** ✅ Implementiert (2026-06-21) **Datenmodell (WorkTask):** - `ParentTaskId` (Guid?) — verknüpft Child mit Parent - `IsAgentTask` (bool) — markiert programmatisch erstellte Agent-Tasks - `ExpectedFrom` (string?) — wer als nächstes antworten soll - `AssignedTo` (string?) — operativer Owner der Task **State Machine:** ``` Parent: Backlog → InProgress → Review → Done ↘ Blocked → Backlog Child: Backlog → InProgress → Done ↘ Blocked → Backlog ``` **Rules (aus TaskStateHelper.CanChangeState):** - **Nur Iris & Bao** dürfen State-Änderungen vornehmen - Sub-Agenten (programmer, reviewer, architekt, researcher, executor) **niemals** - `nexus-system` als technischer Fallback für Cron/Reset-Stale ### 3.2 Bewertung: Architektonisch korrekt | Kriterium | Bewertung | Begründung | |-----------|-----------|------------| | Board als Single Source of Truth | ✅ | Task Board = sichtbare Aufgabenwahrheit | | Delegation sichtbar | ✅ | Child-Tasks statt unsichtbarem Delegations-Status | | Feingliedrige Berechtigung | ✅ | State-Change nur durch Iris/Bao | | Agenten arbeiten gegen Child-Tasks | ✅ | Klare Ownership durch `AssignedTo` | | Parent bleibt bei Iris | ✅ | Parent in `InProgress` während Koordination | | Review-Gate für Bao | ✅ | Parent erst in `Review`, dann Bao-Entscheidung | ### 3.3 Offene Punkte im Board-Modell 1. **Keine automatische Child-Task-Erstellung** — Das Board-Modell setzt voraus, dass Iris manuell Child-Tasks anlegt. Ein strukturierter Workflow für automatische Child-Task-Erstellung bei `spawn`/Subagent-Aufrufen fehlt. 2. **Keine Task→Session-Verknüpfung** — Es gibt keine direkte Verknüpfung zwischen einer Child-Task und der OpenClaw-Subagent-Session, die sie bearbeitet. Der `AgentService` kennt Sessions, `TaskService` kennt Tasks — aber sie sind nicht verknüpft. 3. **Reset-Stale ist ungeschützt** — `POST /api/v1/tasks/reset-stale` hat `[AllowAnonymous]` und kann von jedem aufgerufen werden (siehe Risiko #1). ## 4. Auth- und Rechte-Modell ### 4.1 Authentifizierungsebenen ``` Ebene 1: Browser-JWT (Access Token 15m, Refresh Token HttpOnly Cookie) Ebene 2: X-Nexus-Api-Key (Service-zu-Service, Role=Service) Ebene 3: Gateway-Password (Backend → Gateway, Bearer Authorization) Ebene 4: X-Agent-Id Header (Agent-Identität für Task-State-Enforcement) ``` ### 4.2 Berechtigungsmatrix | Aktion | Bao | Iris | Sub-Agent | nexus-system | Service (ApiKey) | |--------|-----|------|-----------|--------------|-------------------| | Task State ändern | ✅ | ✅ | ❌ | ✅ (intern) | ❌ | | Task Inhalt editieren | ✅ | ✅ | ✅ | ✅ | ✅ | | Agent-Config lesen | ✅ | ✅ | ❌ | ❌ | ✅ | | Gateway-Tool aufrufen | ❌ | ❌ | ❌ | ❌ | ✅ (intern) | | Dashboard-Metriken sehen | ✅ | ✅ | ❌ | ❌ | ✅ | ### 4.3 Bewertung **Positiv:** - JWT-Sicherheit entspricht Best Practices (PBKDF2-SHA256, 210k Iterationen, Rotating Refresh Tokens) - Refresh-Token-Reuse-Detection verhindert Token-Theft - Rate-Limiting auf Login und Refresh - CSRF-Protection via `X-CSRF-TOKEN` + `nexus-csrf` Cookie - Security Headers (HSTS, CSP, XFO, Referrer-Policy) **Kritisch:** - `[AllowAnonymous]` auf `/tasks/board` und `/tasks/reset-stale` — siehe Risiko-Analyse - Kein Scoped-ApiKey — der `X-Nexus-Api-Key` gibt volle Service-Rechte - Keine Audit-Protokollierung für ApiKey-Nutzung ## 5. Gateway-Integration: Sicherheitsanalyse ### 5.1 Tool-Invoke-Pfad (Ist-Stand) ``` POST /api/v1/operations/snapshot → DashboardService → OpenClawGatewayClient.InvokeToolAsync() → POST http://openclaw-gateway-bao:18789/tools/invoke Authorization: Bearer ``` **Aufgerufene Tools (durch Nexus-Backend):** - `session_status` — Agent-Status abfragen (read-only) - `sessions_list` — Session-Liste (read-only) - `sessions_history` — Chat-Verlauf (read-only) - `memory_search` — Memory-Suche (read-only) - `sessions_send` — Chat-Nachricht senden (write, aber kontrolliert) - `cron` — Cron-Jobs verwalten (write) **Gateway HTTP Deny-List blockiert:** - Alle Exec-Tools (exec, spawn, shell) - Alle Filesystem-Tools (fs_write, fs_delete, fs_move, apply_patch) - Gateway-Control-Plane (gateway) - Node-Relay (nodes) - Session-Orchestrierung (sessions_spawn, sessions_send) ### 5.2 Docker-Netzwerk & Gateway-Bind **Aktueller Stand (2026-07-09):** ``` compose.yaml: api: extra_hosts: - host.docker.internal:host-gateway networks: - nexus - openclaw_default Gateway-Konfiguration: gateway.bind: "lan" Nexus-Konfiguration: OPENCLAW_BASE_URL=http://openclaw-gateway-bao:18789 ``` **Ergebnis:** - Nexus erreicht das Gateway direkt über Docker-DNS im gemeinsamen `openclaw_default`-Netz. - Der Umweg über einen nicht veröffentlichten Host-Port entfällt. - Der produktive Aggregat-Healthcheck prüft neben PostgreSQL auch die Runtime-Verbindung. Der frühere Pfad `host.docker.internal:18789` war auf dem VPS nicht erreichbar und ist obsolet. Produktive Einstellung: ```yaml Integrations__OpenClaw__BaseUrl: http://openclaw-gateway-bao:18789 ``` Beide Container müssen Mitglied im `openclaw_default`-Netzwerk sein. ### 5.3 MCP-artige Integration: Bewertung **Das Gateway `/tools/invoke` ist bereits MCP-artig:** - JSON-RPC-ähnliche Aufrufe mit `tool` + `args` + `sessionKey` - Strukturierte Responses mit `{ ok, result, error }` - Tool-Discovery via Policy (Deny/Allow-List) - Request/Response mit eindeutiger Fehlersemantik **Was fehlt für ein vollständiges MCP-Interface:** - Keine Tool-Listing/Discovery über API (kein `tools/list`) - Keine Schema-Validierung für Tool-Arguments - Keine Structured Outputs (function-calling-ähnliches Format) **Empfehlung: NICHT ein MCP-Protokoll zwischen Nexus und Gateway einführen.** Stattdessen den bestehenden `/tools/invoke`-Pfad weiter nutzen und strukturieren. ### 5.4 Neue Backend-Bridge (Implementiert 2026-06-22) Der Nexus-eigene strukturierte Kommando-Adapter wurde eingeführt: ``` Nexus Backend ├─ GatewayToolClient (bestehender OpenClawGatewayClient) │ └─ POST /tools/invoke (Gateway) │ ├─ GatewayBridgeController (NEU — /api/bridge/) │ ├─ POST /api/bridge/tasks (create_task) │ ├─ POST /api/bridge/tasks/{id}/children (create_child_task) │ ├─ PATCH /api/bridge/tasks/{id}/status (update_status) │ ├─ POST /api/bridge/tasks/{id}/activity (append_activity) │ ├─ POST /api/bridge/tasks/{id}/handoff (handoff) │ ├─ GET /api/bridge/board (get_board) │ ├─ GET /api/bridge/tasks/{id} (get_task) │ ├─ GET /api/bridge/tasks/{id}/children (get_children) │ ├─ GET /api/bridge/tasks/{id}/activity (get_activity) │ └─ GET /api/bridge/agent-overview (get_agent_overview) │ ├─ ITaskBridgeService / TaskBridgeService (NEU) │ └─ Typisierte TaskBridgeResult mit Outcome: Success/NotFound/InvalidState/Unauthorized/ValidationError │ ├─ BoardOrchestrationService (offen) │ ├─ Tasks erstellen/aktualisieren │ ├─ Session-Status abfragen │ └─ Agent-Progress berechnen │ └─ AgentDelegationService (offen) ├─ Subagent-Task anlegen ├─ Session verfolgen └─ Ergebnis integrieren ``` **Auth-Modell für /api/bridge:** - Primär: `X-Agent-Id` Header (Agent-Identität vom Gateway) - Fallback: JWT (Browser-authenticated user → bao) - Fallback: `X-Nexus-Api-Key` (Backend-zu-Backend Service-Identität) - Rate-Limiting: 30 Requests/Minute (agents-Policy) **Das Frontend sieht /api/bridge NICHT.** Der Pfad ist ausschließlich für Agent-zu-Backend-Kommunikation. ## 6. Risikoanalyse ### 6.1 KRITISCH — `[AllowAnonymous]` auf Board-Endpunkten ✅ BEHOBEN (2026-06-22) **Betroffen (vorher):** ```csharp // TasksController.cs [AllowAnonymous] [HttpGet("board")] // Gab ALLE Tasks zurück — inkl. Detail-Texte [AllowAnonymous] [HttpPost("reset-stale")] // Konnte Tasks zurücksetzen — datenändernd ``` **Fix angewandt:** - `[AllowAnonymous]` entfernt - Inline-Auth-Check: `X-Agent-Id` Header, ApiKey-Rolle, oder JWT erforderlich - `reset-stale` erfordert zusätzlich `X-Agent-Id: iris` Identität (nur Iris darf) - Neuer `/api/bridge/` Pfad als sauberer Agent-zu-Backend-Adapter - Rate-Limiting (agents-Policy: 30/min) auf Bridge-Endpunkte ### 6.2 MITTEL — Keine Task→Session-Verknüpfung Wenn ein Subagent eine Child-Task bearbeitet, gibt es keine technische Verknüpfung zwischen der Child-Task und der OpenClaw-Session. Das bedeutet: - Keine automatische Status-Aktualisierung bei Session-Abschluss - Keine Sitzungs-Historie direkt von der Task aus erreichbar - Iris muss manuell prüfen, ob ein Agent fertig ist **Empfehlung:** - `WorkTask` um `SessionKey` (string?) erweitern - Bei Child-Task-Erstellung Session-Key speichern - Status-Polling: Wenn Session inaktiv, Child-Task auf Done/Blocked prüfen ### 6.3 MITTEL — Gateway-Passwort in Config-Dateien Das Gateway-Passwort `ieDmOjBiVfbbDM0ibrEebPAg` ist: - In `.env` auf dem Host (OK) - In `gateway-api-research.md` (maskiert: `ieDm...PAg`) - Im `openclaw.json` auf dem Host (OK) - In der API-Container-Umgebungsvariable (notwendig) **Empfehlung:** - Gateway-Rate-Limiting aktiv halten (10 attempts/60s → 5min lockout) - Gateway-Bind auf `lan` ändern (nicht öffentlich exponiert) - `gateway-api-research.md` aus dem öffentlichen Repo entfernen oder Passwort entfernen ### 6.4 NIEDRIG — Keine Scoped API-Keys Der `X-Nexus-Api-Key` gibt volle Service-Rechte. Es gibt keine Möglichkeit, verschiedene API-Keys mit unterschiedlichen Rechten zu vergeben. **Empfehlung (spätere Phase):** - API-Key-Scopes einführen (read, write, admin) - Rate-Limiting pro API-Key - Audit-Log für ApiKey-Nutzung ### 6.5 NIEDRIG — Kein Structured Output vom Gateway Die `/tools/invoke`-Responses sind JSON, aber ohne Schema-Garantie. Die Backend-Logik extrahiert Felder mit vielen Fallbacks (`??`-Kettenantworten). **Empfehlung:** - Response-Typen für jedes Tool definieren (DTOs) - Deserialisierung mit Schema-Validierung - Fallback-Logik zentralisieren ## 7. Migrationsreihenfolge (Vorschlag) ### Phase 3b — Sichere Board-Grundlage (JETZT) ``` 1. [AllowAnonymous] auf /tasks/board und /tasks/reset-stale fixen → ApiKey-Auth plus X-Agent-Id-Validierung → README/Iris-Doku aktualisieren 2. Gateway-Bind von loopback auf lan ändern → API-Container über openclaw_default Netzwerk ansprechen → host.docker.internal-Fallback entfernen ``` ### Phase 4 — Strukturierte Orchestrierung ``` 3. Task→Session-Verknüpfung einführen → WorkTask.SessionKey (string?) → Bei Child-Task-Erstellung Session speichern 4. AgentDelegationService → Zentralisierte Subagent-Task-Erstellung → Session-Status-Monitoring → Automatische Status-Propagation (Session done → Task done) 5. BoardOrchestrationService → Refresh-Intervall für Agent-Progress → Stale-Erkennung mit Session-Status → Priorisierung nach Workload ``` ### Phase 5 — Erweiterte Integration ``` 6. Structured Tool Responses → DTOs für jedes Gateway-Tool → Schema-Validierung → Caching für häufige Abfragen 7. API-Key-Scopes → read/write/admin Scopes → Audit-Log für ApiKey-Nutzung 8. Automatische Child-Task-Erstellung → Bei spawn/subagent-Aufrufen automatisch Child-Task anlegen → Session-Key verknüpfen → Activity-Feed erweitern ``` ## 8. Sichere Brücke: Architekturprinzipien ### 8.1 Das Backend ist die einzige Brücke ``` Browser ←→ Nexus API ←→ OpenClaw Gateway ↑ ↑ JWT Auth Gateway Password (pro User) (nur im Backend) ``` **Niemals:** - Gateway-Passwort im Frontend - Direkter Browser→Gateway API-Call - MCP-Protokoll zwischen Frontend und Gateway - Agent-Sessions direkt aus dem Frontend steuern ### 8.2 Prinzipien für jede neue Integration 1. **Neue Endpunkte immer im Nexus-Backend** 2. **Auth über bestehendes JWT/ApiKey-System** 3. **Gateway-Calls immer serverseitig mit Gateway-Passwort** 4. **Kein Gateway-Tool direkt aus dem Frontend aufrufen** 5. **State-Änderungen nur durch Iris/Bao (via Backend-Enforcement)** 6. **Activity/Audit für jede State-Änderung** ### 8.3 Strukturierte OpenClaw-Integration (MCP-artig) Der Gateway `/tools/invoke`-Endpunkt ist bereits strukturell MCP-artig. Eine formale MCP-Implementierung zwischen Nexus und Gateway ist **nicht notwendig**. Stattdessen: ``` Nexus.Backend.Services ├── IOpenClawGatewayClient (Gateway-Tool-Abstraktion) │ └── InvokeToolAsync(tool, args) → JsonNode? │ ├── IBoardOrchestrationService (NEU) │ ├── CreateDelegateTask(agentId, title, detail) → WorkTask │ ├── WatchAgentSession(agentId) → SessionWatcher │ └── SyncAgentProgress() → Progress[] │ └── IAgentDelegationService (NEU) ├── DelegateToAgent(parentTaskId, agentId, instruction) ├── CollectResult(subTaskId) → AgentResult └── HandleBlocker(subTaskId, reason) → void ``` ## 9. Zusammenfassung ### Was gut ist (nicht ändern): - Board-first-Ansatz mit Parent/Child-Tasks - Backend als einzige Gateway-Brücke - JWT + Gateway-Password-Trennung - State-Change-Restriktion auf Iris/Bao - HTTP Deny-List des Gateways - CSP im Frontend (kein externer Connect) ### Was verbessert werden muss: - `[AllowAnonymous]` auf Board-Endpunkten → ApiKey-Auth - Gateway-Bind loopback → lan (oder Netzwerk-Routing korrigieren) - Task→Session-Verknüpfung fehlt ### Was später kommen kann: - Automatische Child-Task-Erstellung bei Subagent-Aufrufen - Structured Gateway Responses mit Schema-Validierung - API-Key-Scopes und Audit - Session-gesteuertes Progress-Tracking ### Was niemals kommen darf: - Gateway-Passwort im Frontend - Direkter MCP-Pfad Browser→Gateway - Agent-Session-Steuerung aus dem Frontend - Task-State-Änderung durch Sub-Agenten