Files
nexus/docs/agent-identity-architecture.md
T
developer a55951f315
CI - Build & Test / Backend (.NET) (push) Successful in 31s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 17s
CI - Build & Test / Security Check (push) Successful in 3s
CI - Build & Test / Deploy Nexus (push) Failing after 19s
P4: Agent-Identitäten ohne Secrets — sanitized config feed
- Replace Python-based sanitizer in deploy script with lightweight jq/alpine
- Add sync-agents-sanitized.mjs for on-demand and watch-mode sync
- Add AgentConfigPath to appsettings.json (explicit default)
- Extend /health/live endpoint to report agent count from sanitized config
- Document architecture in docs/agent-identity-architecture.md
- No openclaw.json secrets ever reach Nexus API containers

Verification:
- curl /api/v1/agents → 9 agents, zero secrets in response
- agents-sanitized.json contains only 'agents' key, no gateway/auth
- All C# code paths read from agents-sanitized.json (AgentConfigPath)
- Bridge controller resolves agent IDs via AgentService.GetAllowedAgentIdsAsync()
2026-07-13 09:12:19 +02:00

3.3 KiB

Agent Identity Architecture (P4)

Status: Implemented (2026-07-13) Task: 4291d694-dd40-410d-b0d5-4742d334547a

Problem

Nexus needed agent identity data (id, name, role, sub-agents, model) for the board/bridge operations, but reading directly from /home/node/.openclaw/openclaw.json would expose secrets (gateway password, API keys, auth profiles, channel tokens).

Solution: Sanitized Agent Config File

Architecture

openclaw.json (full config, SECRETS)
    │
    ├── Deploy-time: jq extract → agents-sanitized.json (NO secrets)
    │   └── deploy-nexus.sh: extracts only {"agents": ...} from openclaw.json
    │
    ├── Manual sync: scripts/sync-agents-sanitized.mjs
    │   └── node scripts/sync-agents-sanitized.mjs --once
    │
    └── Nexus API reads: agents-sanitized.json (read-only mount in compose)
        ├── AgentService.LoadAgentConfigsAsync()
        ├── OpenClawGatewayClient.LoadAgentIdsFromConfig()
        └── AgentService.GetAllowedAgentIdsAsync()

Key Design Decisions

  1. Single sanitized source: agents-sanitized.json contains ONLY the agents key (list + defaults) — no gateway, auth, channels, tools, plugins, etc.

  2. Read-only mount: Compose mounts as :ro — no write access from the API container

  3. No ACL dependency: No uid-1654 ACL needed; the sanitized file is root-owned and world-readable

  4. Graceful fallback: If the sanitized file is missing, both AgentService and OpenClawGatewayClient fall back to hardcoded agent IDs from AgentIdentityCatalog

  5. Auto-sync on deploy: The deploy pipeline (deploy-nexus.sh) regenerates the sanitized file from openclaw.json using jq in an alpine container

  6. Manual sync available: scripts/sync-agents-sanitized.mjs provides on-demand and watch-mode sync

File Layout

File Location Purpose
openclaw.json /home/node/.openclaw/openclaw.json Full config with secrets (gateway only)
agents-sanitized.json /home/node/.openclaw/agents-sanitized.json Agents-only, no secrets
Compose mount compose.yaml → API container agents-sanitized.json:ro
Deploy sanitizer .gitea/scripts/deploy-nexus.sh jq extraction on deploy
Sync script scripts/sync-agents-sanitized.mjs Node.js manual/watch sync
Config path backend/appsettings.json AgentConfigPath key

Security Guarantees

  • No password, token, secret, or api_key values in agents-sanitized.json
  • API endpoints (/api/v1/agents, /api/v1/agents/{id}) return ZERO secrets
  • Gateway bridge controller (/api/bridge/*) uses only agent IDs from sanitized config
  • No direct openclaw.json reads in any C# code path
  • Agent identity catalog (AgentIdentityCatalog) is a hardcoded fallback, not a primary source

Verification

# Check sanitized file has no secrets
curl -s http://nexus-api-1:8080/api/v1/agents \
  -H "X-Api-Key: <key>" | grep -i "password\|secret\|token\|apikey"
# Expected: no output

# Verify only "agents" key exists in sanitized file
python3 -c "
import json
with open('agents-sanitized.json') as f:
    data = json.load(f)
print(list(data.keys()))  # Should print ['agents']
"