OpenBot Architecture Deep Dive: Giving AI Agents a Real Computer Without Burning Down Your Company
*Structured English summary of a long-form Chinese technical article on zhichai.net. The original includes hands-on attack tests, full architecture diagrams, and code samples.*
Key points
- What OpenBot is: An open-source, self-hosted enterprise agent platform (a template, not a hosted product — all workspaces are
"private": true, no npm packages). It gives agents a ChatGPT/Claude-style UI while locking each agent into an isolated, controlled computer container. - The problem it solves: Real office automation needs real browsers (legacy systems, 2FA login walls), framework-agnostic governance (agent frameworks churn every few months), tool-list noise reduction (200+ tool definitions wreck model accuracy), and safe unattended execution (prompt injection at 3 a.m.).
- Extracting
DATABASE_URLviaenvin the agent's shell: nothing leaked — even proxy credentials are stripped. - Creating a dangling symlink to
/etc/cron.d/backdoor: blocked with403via a manuallstat/readlinkwalk up to 32 hops before any write. TRUNCATE audit_events;on the database: rejected —Audit events are append-only(enforced by a migration literally named0012_truncate_is_not_a_way_around_append_only.sql).- Path A (single container): one
docker runwithEMBEDDED_POSTGRES=onon port 3001. Pitfall: mount the volume at/var/lib/postgresql, not/var/lib/postgresql/data— alost+foundfolder makesinitdbfail silently. - Path B (source dev stack): requires Docker, Bun 1.4.3+, Node/npx;
scripts/start.shauto-generates two distinct tokens (MANAGED_AGENT_TOKENserver→agent,AGENT_TOOL_TOKENagent→gateway) with automatic restart on rotation. - Declarative coworkers: tenants are pure YAML (
brand.yaml,agents.yaml,skills.yaml,channels.yaml); hiring a new bot is copying a catalog YAML file. Theme CSS is validated to block@import/url()exfiltration. - Anti-self-deception lesson: An unconditional "tell users you have no access when not connected" instruction caused the agent to claim no access *even after tools returned real files*. Fix: condition on observable facts (tool returned results = connected) instead of moral slogans.
- Skills as a noise funnel: When granted tools exceed
SELECTION_FLOOR = 12, a lightweight model matches relevant skills and the offered tool set becomesT_granted ∩ (union of skill-declared tools ∪ unclaimed tools). Skills never grant permissions (outer intersection is the security floor), and this narrowing layer deliberately fails open — it is a usability layer, not a security boundary. The gateway remains fail-closed. - 15 sample brains, zero private SDK: none install any OpenBot SDK. Two integration paths:
- Path A (13 of 15 samples): tools arrive as JSON Schema in the AG-UI request; server-side tools are intercepted in-gateway by
HeadlessToolsMiddleware(policy, audit, human approval asinterrupt) and the agent is re-invoked with results — no gateway calls in agent code. - Path B (graph-internal callbacks): for multi-step LangGraph graphs, agents call
POST /api/agent-tools/callwithAGENT_TOOL_TOKENplus an HMAC-SHA256-signed 10-minuteopenbotRunassertion, cross-verified against the static token identity to prevent impersonation. - Python example (~30 lines): FastAPI +
pydantic-ai+AGUIAdapter.dispatch_request, with constant-time token comparison middleware; endpoints must be explicitly allow-listed viaAGENT_ENDPOINT_ALLOWED_HOSTS(no wildcards, SSRF protection). - Routine guardrails: minimum 15-minute interval verified by simulating 200 future firings from a leap-year-safe anchor; max 20 concurrent routines per person; automatic fatigue circuit-breaker after 10 consecutive failures.
Architecture overview
Four building blocks plus one iron rule — every tool call passes through a single gateway:
| Module | Port | Role |
| :--- | :--- | :--- |
| app/ | 3010 (dev) / 3001 (single container) | React 19 main console: channels, live screen, admin |
| server/ | 3001 | Bun + Hono control plane: auth, context assembly, the single gateway |
| agent-computer/ | 4100 (loopback only) | Sandboxed container: headed Chromium, /workspace, sanitized Bash |
| supervisor/ | 4500 | Sole holder of read-only Docker socket; provisions per-person computer containers |
| worker/ | none | 30-second routine poller with DB row locks |
| agent-* (15 samples) | 4200–4214 | AG-UI sample brains: LangGraph, CrewAI, Pydantic AI, Claude SDK, Mastra, etc. |
Governance rides on the protocol, not the framework: OpenBot only requires agents to speak AG-UI (standard JSON input, SSE event stream). A brain can run anywhere in any language; the gateway still decides what it may do.
Security gateway: five key mechanisms
The gateway enforces a fixed order: resolve target → evaluate CEL policy → write audit row → execute.
1. Zero-DOM-pollution snapshots & anti-DNS-rebinding: Uses Playwright's ariaSnapshot() to extract the accessibility tree (17 interactive control types, elements referenced as e5, etc.). The gateway never trusts the agent's claimed element names — it re-resolves e5 from its own server-side computer_snapshot table, checking session/snapshot generation (409 StaleSnapshotError on mismatch). Egress goes through a local filter proxy doing exactly one DNS resolution, then connecting to the validated literal IP — DNS rebinding is physically impossible. Cloud metadata hosts (169.254.169.254, etc.) are hard-blocked, including IPv6-mapped disguises.
2. Asymmetric fail-closed CEL policies: Deny rules evaluate before allow rules; nothing is allowed by default. A broken deny rule evaluates as *deny* (error → true); a broken allow rule evaluates as *no-allow* (error → false). Intent normalization groups clicks, submit-typed inputs, and Enter/Space keypresses into one activate intent so models can't bypass rules. An initiator.kind field distinguishes person from routine, enabling rules like "block all writes/commands when no human is watching."
3. Write-before-act auditing: Audit rows are await-written to PostgreSQL *before* execution — if the DB is down, actions never fire. The audit_events table is append-only with row-level and BEFORE TRUNCATE triggers; CSV export defends against formula injection and logs exports themselves.
4. Credential blindness: Agents can never receive passwords. Users type credentials into out-of-band secure forms; the container verifies page origin before filling (anti-phishing), and secret inputs are masked in all snapshots/screenshots ("[hidden: sensitive field]") via DOM tagging plus CSS text security.
5. No queuing on human takeover: When a human takes control, in-flight actions drain and all new agent actions (including shell and file writes) get an instant 409 Conflict — never queued or replayed, since the page state has changed. Stale snapshots are invalidated so the agent must re-observe before acting.
Hands-on validation (the author's three "knife tests")
Setup and operations
Prompt engineering and tool pruning
Integrating your own agents
Takeaway
OpenBot's central thesis: "can call your tools" and "you dare let it near production tools" are separated by an entire customs bureau. By pinning governance to an open protocol (AG-UI) and enforcing one audited, fail-closed gateway, it lets teams use any agent framework while keeping secrets out of model context, blocking DNS rebinding and symlink attacks, and producing an append-only audit trail that survives even deliberate TRUNCATE attempts.
*Original article by the zhichai.net community; all technical details, file names, and code excerpts are drawn from the OpenBot repository as cited in the source post.*