Trust Zones
Four zones plus a 2.5 operator-or-internal tier (mirrors the table in Architecture):| Level | Zone | Notes |
|---|---|---|
| 0 | Untrusted | External input. Webhook bodies 1MB cap, optional HMAC-SHA256, all messages sanitized via sanitize_for_prompt(). |
| 1 | Sandboxed (agents) | Hardened agent containers (see below). Hold no LLM keys — proxy through mesh. |
| 2 | Trusted (mesh) | Mesh host. Holds credentials, manages containers, routes messages. |
| 2.5 | Operator-or-internal | _require_operator_or_internal gate on fleet-wide metrics, per-agent metrics, stale tasks, audit/archive endpoints. |
| 3 | Loopback-only | Requires both x-mesh-internal: 1 header and loopback IP. Caddy strips the header from inbound public traffic on managed VPSes. |
Defense-in-Depth Layers
| Layer | Mechanism | What it prevents |
|---|---|---|
| Runtime isolation | Docker containers (default) or Docker Sandbox microVMs (opt-in via --sandbox) | Agent escape, kernel exploits |
| Container hardening | UID 1000, cap_drop=ALL, no-new-privileges, read-only root FS, tmpfs=/tmp 100m noexec/nosuid, pids_limit=256, 384MB / 0.15 CPU (worker) | Privilege escalation, resource abuse |
| Browser-container egress | iptables OUTPUT REJECT for RFC1918, loopback, link-local, CGNAT, IANA-reserved IPv4+IPv6; allows in-container loopback; fail-closed | SSRF/data exfil from the browser tier |
| Credential separation | Two-tier vault: OPENLEGION_SYSTEM_* mesh-only; OPENLEGION_CRED_* agent-tier gated by glob. Opaque $CRED{name} handle substitution at call time. | Key leakage, unauthorized API use |
| Permission enforcement | Per-agent ACLs (default deny) for messaging, pub/sub, blackboard globs, allowed_apis, browser_actions, wallet, control-plane flags | Unauthorized data access, capability escalation |
| Input validation | Path traversal (4-stage check), AST validation for create_skill, SSRF protection, safe condition eval (no eval()), bounded execution caps | Injection, runaway loops |
| Unicode sanitization | Invisible-character stripping at 56 call sites across user input, tool results, and system-prompt context | Prompt injection via hidden Unicode |
Dual Runtime Backend
| Docker Containers (default) | Docker Sandbox microVMs | |
|---|---|---|
| Isolation | Shared kernel, namespace separation | Own kernel per agent (hypervisor) |
| Escape risk | Kernel exploit could escape | Hypervisor boundary — much harder |
| Performance | Native speed | Near-native (Rosetta 2 on Apple Silicon) |
| Requirements | Any Docker install | Docker Desktop 4.58+ |
| Enable | openlegion start | openlegion start --sandbox |
SandboxBackend initialization can fail (Docker Desktop too old, hypervisor disabled, etc.). On failure it falls back to DockerBackend automatically and logs the reason.
Container Hardening (full set)
Worker containers run with the following enforced flags (engine/src/host/runtime.py):
- UID 1000 (non-root).
- 384MB RAM, 0.15 CPU quota.
pids_limit=256.cap_drop=ALL,no-new-privileges.read_only=Trueroot filesystem.tmpfs=/tmp100MB,noexec/nosuid.
NET_ADMIN, SETUID, SETGID capabilities to run iptables and gosu to UID 1000. Its egress is locked down by the iptables filter described above.
Credential Vault
Agents never hold API keys. The vault uses a two-tier prefix system:OPENLEGION_SYSTEM_*— LLM provider keys, channel tokens, master secrets. Never agent-accessible.OPENLEGION_CRED_*— agent-tier tool/service keys. Access controlled per-agent viaallowed_credentials(fnmatch glob, case-insensitive;["*"]= all agent-tier; system creds always blocked).
- The vault loads credentials from both prefix sets at startup.
- Agents call APIs through the mesh proxy. Their tool args may reference
$CRED{name}— an opaque handle, not the secret value. - The mesh substitutes the real value server-side before forwarding to the provider.
- Budget limits are checked before dispatching LLM calls; usage is recorded after.
CAPTCHA_SOLVER_KEY, _SECONDARY, CAPTCHA_SOLVER_PROXY_LOGIN, _PASSWORD) intentionally bypass the standard vault. They live in env only and are stripped from config/settings.json at load.
Permission Matrix
Per-agent ACLs inconfig/permissions.json. Default policy: deny; missing file means deny-all.
Permissions cover:
- Messaging:
can_message(list of target agent IDs). - Pub/Sub:
can_publish,can_subscribe(topic lists). - Blackboard:
blackboard_read,blackboard_write(glob patterns). - APIs:
allowed_apis,allowed_credentials(glob over agent-tier creds). - Browser:
can_use_browser,browser_actions(None/["*"]= all known actions;[]= deny; specific list = allowlist). - Cron:
can_manage_cron. - Spawning:
can_spawn. - Wallet:
can_use_wallet,wallet_allowed_chains,wallet_spend_limit_per_tx_usd,wallet_spend_limit_daily_usd,wallet_rate_limit_per_hour,wallet_allowed_contracts. - Control-plane (six flags, operator-default
true, workersfalse):can_manage_fleet,can_manage_projects,can_edit_agent_config,can_view_fleet_metrics,can_route_tasks,can_request_user_credentials.
can_spawn=true or can_use_wallet=true. Those capabilities require a human operator outside the chat surface.
Input Validation
- Path traversal protection — agent file operations are confined to
/datavia a 4-stage check (strip/data/prefix, reject..lexically,lstat()symlink-safe walk, finalis_relative_to("/data")). - AST validation for skill self-authoring —
create_skillruns the candidate through an AST validator with 23 forbidden imports, 16 forbidden calls, 11 forbidden attribute accesses, plus a 10,000-char size cap (_MAX_SKILL_SIZE). - Safe condition evaluation — workflow/condition parsing uses a regex-based safe parser, never
eval(). - Bounded execution — task loops cap at
MAX_ITERATIONS=20; chat atCHAT_MAX_TOOL_ROUNDS=30per turn /CHAT_MAX_TOTAL_ROUNDS=200per session /_MAX_SESSION_CONTINUES=5; heartbeats atHEARTBEAT_MAX_ITERATIONS=12. - Token budgets — per-agent daily (1000) and monthly (30000) caps enforced before dispatch.
- Tool-loop detection — warn @ 2 repeats, block @ 4, terminate @ 9.
SSRF Protection
The HTTP tool runs every outbound request through a multi-layer SSRF filter:- DNS pinning — resolve once, reuse the same IP for the actual connection.
- Blocklist — RFC1918, loopback, link-local, CGNAT (
100.64.0.0/10), 6to4 (2002::/16), Teredo, IPv4-mapped IPv6. - Fail-closed on DNS error — any resolution failure rejects the request.
- Max 5 redirects, re-validated at each hop.
- Cross-origin auth strip — Authorization headers are removed when redirecting to a new origin.
Dashboard & VNC
- CSRF — state-changing dashboard endpoints require an
X-Requested-Withheader. - CSP — the dashboard sets a CSP, but it allows
unsafe-inlineandunsafe-eval(Alpine.js requirement). Jinjaautoescape=Trueis the primary XSS defense, not CSP. - VNC proxy at
/agent-vnc/{agent_id}/{path}— rejects agent Bearer tokens and requires theol_sessioncookie on both HTTP and WebSocket upgrade. Agent credentials cannot leak through a browser session. - Authentication — dev/self-hosted is open if
/opt/openlegion/.access_tokenis absent. Hosted requires anol_sessioncookie verified via HMAC (24h max age + 5-min skew). The SSO callback/__auth/callbackand HMAC verification live in the upstream Caddy auth-gate sidecar — the engine itself only consumes the cookie.
Wallet Seed Reveal
The master mnemonic is generated byPOST /api/wallet/init and returned once with Cache-Control: no-store. Every subsequent call to GET /api/wallet/seed returns HTTP 410 Gone — there is no second-chance reveal. Private keys derived from the seed (BIP-44 for EVM, HMAC-SHA512 over PBKDF2 for Solana) never leave the mesh process.
What This Does NOT Do
OpenLegion is small and honest about its limits. The following are not part of the threat model:- No “zero-trust” claim. We use trust zones + defense-in-depth.
- No compliance certifications (SOC 2, ISO 27001, HIPAA). The engine is v0.1.0.
- Engine SQLite databases are not encrypted at rest. Only
.envischmod 0o600; the provisioner separately encrypts SSH keys at rest with Fernet. - JWT sessions are non-revocable (NextAuth JWT strategy is stateless).
- App rate limiter is in-memory, single-region — no global rate limits.
_blackboard_xproject_countandtool_denials_24hare observability-only, not enforcement.- No air-gapped mode. All agents need mesh egress for LLM proxy.
- The engine does not implement SSO end-to-end. HMAC verification, replay protection, and cookie issuance live in the auth-gate sidecar shipped via cloud-init on managed VPSes — not in engine code.
