Security

RoboCo runs a workforce of agents with access to your repositories, so its security model is worth understanding before you put it anywhere. The short version: it is built for a trusted private network, it ships in a permissive header-trust mode by default, and you harden it with one flag and one token. This page covers the trust model, how to lock it down, where secrets live, and the one rule that matters more than the rest.

Do not expose RoboCo to the public internet

RoboCo is designed to run on a trusted LAN behind nginx, which is the only externally-exposed service. The default authentication mode trusts request headers (below), and one WebSocket stream is unauthenticated by design. Treat the whole system as you would a database: reachable only from your own network, never from the open internet.

How is the orchestrator API kept off the open network?

The orchestrator's :8000 binds to 127.0.0.1 only — it is not published on a routable host interface. Both deploy composes used to publish :8000 on 0.0.0.0, so anyone who could reach the host hit the control plane directly: bypassing nginx and, in the default header-trust posture below, reading and writing runtime settings or spoofing X-Agent-Role: ceo to spawn/stop agents with no credential at all (GHSA-4f7g-w95g-5q2c).

nginx reaches the orchestrator over the internal Docker network, so normal operation and on-host debugging (curl http://localhost:8000/health run from the host itself) are unchanged. Off-host access has to go through nginx — and, if you've armed it, cloud auth — same as every other route. The header-trust design itself is unchanged; this closes the unintended off-host reachability that gave a header spoof any teeth.

What is the default header-trust mode?

By default the API authenticates an agent purely from request headers: X-Agent-ID, X-Agent-Role, and optionally X-Agent-Team. There is no signed token required, which means any client that can reach the API can claim any role — including ceo. This is intentional for a single-operator deployment on a trusted network (it keeps the panel and curl-for-debugging simple), and the API logs a loud warning at startup whenever it's in this mode (roboco/api/app.py):

Agent auth is in HEADER-TRUST mode … the API accepts X-Agent-Id / X-Agent-Role without verifying a signed token, so any client that can reach it may act as any role, including 'ceo'. Acceptable only on a trusted private network.

If your network boundary is solid, header-trust is fine. If it isn't — or you just want defense in depth — turn on secure mode.

How do I harden RoboCo with secure mode and the panel token?

Set ROBOCO_AGENT_AUTH_REQUIRED=true to require a signed token on every request. In this mode:

  • Every request must carry an X-Agent-Token that is an HMAC of agent_id:role:team signed with ROBOCO_AGENT_AUTH_SECRET. The orchestrator mints this token for each agent at spawn, so agent traffic keeps working transparently.
  • A missing token is rejected with 401. A forged role won't help: even in header-trust mode, any token that is presented is still verified, so you can't bypass auth by supplying an invalid token. The HMAC comparison is constant-time.
  • The panel authenticates as the CEO, so it needs a CEO token to keep working in secure mode. Mint it with make panel-token, which prints the signed token (and refuses with an error if ROBOCO_AGENT_AUTH_SECRET is unset, since an unsigned token would be useless). Configure the panel with that token.
Set the secret before you flip the flag

ROBOCO_AGENT_AUTH_SECRET is the signing key for every agent and panel token. Set it first, then make panel-token, then set ROBOCO_AGENT_AUTH_REQUIRED=true — otherwise agents and the panel can't produce valid tokens and every request 401s. If the secret is unset, token minting fails closed to a literal UNSIGNED value rather than producing a usable token.

See API authentication for the exact headers and how the panel and external clients pass them.

How does per-agent token binding work?

The token isn't a single shared bearer secret — it's bound to the specific agent. Because the HMAC covers agent_id:role:team, a token issued for be-dev-1 as a developer is only valid for those exact header values. An agent on the Docker network can't take its own valid token and replay it claiming to be main_pm or ceo; the signature won't match the forged role, and the request is rejected. This is what stops one agent from escalating its own privileges by editing headers.

How are secrets handled?

Three secrets are load-bearing, and all three stay out of the repo:

SecretWhat it protectsRule
ROBOCO_ENCRYPTION_KEYFernet key that encrypts every per-project git token (GitHub, Gitea, or GitLab) at restSet once at install; never change it or all stored tokens become undecryptable. Back it up.
ROBOCO_AGENT_AUTH_SECRETHMAC signing key for agent and panel tokensRequired for secure mode; keep it off the repo and out of logs.
ROBOCO_CLOUD_AUTH_SECRETSigning key for the cloud auth session cookie's JWTRequired when ROBOCO_CLOUD_AUTH_ENABLED=true — the orchestrator refuses to start without it. Irrelevant, and unused, while cloud auth is off.

Project git tokens are encrypted the moment you save them (with ROBOCO_ENCRYPTION_KEY) and stored in the projects.git_token_encrypted column — the same treatment regardless of which forge the project points at. The API never returns a token — responses only carry a has_git_token boolean, so the panel can show whether a token is set but never its value. The X (Twitter) engine's four OAuth 1.0a credentials and the Telegram bridge's bot token get the identical treatment — Fernet-encrypted at rest, entered once in the panel, and never returned by the API. None of these secrets belong in the repository; put them in your .env (which is gitignored) or your secret manager.

How does cloud auth lock the door for your own browser session?

Everything above is about the agent fleet's identity. Cloud auth (ROBOCO_CLOUD_AUTH_ENABLED, off by default) is a separate, orthogonal mechanism for your own login — a single seeded CEO user with a sliding, 30-day session cookie, for when you want to put the panel somewhere beyond a fully trusted LAN.

It's not a general multi-user system: there's no registration route and no way to create a second account. It changes what get_agent_context trusts, too — once armed, a caller claiming the ceo role without either a valid X-Agent-Token or a valid session cookie is rejected outright, closing the header-spoof hole for the one role that matters most. See API auth for exactly how the session cookie and the agent token sit side by side, and Cloud auth for the full setup.

Requires TLS

The session cookie is secure-only, so it only works behind HTTPS. Don't arm this flag until something in front of RoboCo terminates TLS — over plain HTTP the cookie is set but never sent back, and login will look like it silently fails.

Why don't agents need your production database credentials?

Two more default-off subsystems remove a credential agents used to receive outright. When toolchain matching is on, the orchestrator has historically injected RoboCo's own production Postgres credentials into an agent's environment so its self-gate could run against a real database (_append_gate_env). Either of the following now takes that credential away from agents entirely, for the projects/deployments where it applies:

  • DB network isolation (ROBOCO_DB_NETWORK_ISOLATED, on by default in all three tracked compose files) puts postgres/redis on a data-only network agent containers never join — the injection is suppressed because the credentials would dead-end in a connect timeout anyway.
  • Sandboxed dev DB/Redis/Mongo (ROBOCO_SANDBOX_DB_ENABLED, off by default, per-project opt-in) replaces the production credentials with a throwaway sibling container a dev/QA agent requests on demand mid-session (request_sandbox), released when its engagement with the work ends.

Either one means an opted-in agent's gate still runs against a real database — just never RoboCo's own.

Why does the token never enter an agent container?

This is the guarantee that makes it safe to hand RoboCo a private repo: your git token is never present inside an agent container, on any forge. The orchestrator decrypts the token only at the moment of a git operation, injects it for that operation, and immediately after cloning scrubs the token out of the clone's git config — then verifies no token byte survives anywhere under .git/, destroying the workspace if one did. A compromised or misbehaving agent has nothing to exfiltrate, because the credential was never on its disk. The clone scrub is described in Register a project, and the broader sandboxing model in the gateway.

What's the WebSocket auth caveat?

The per-resource WebSocket streams are keyed to a resource, but the operator stream is not authenticated. /ws/system carries no per-agent keying and no token even when ROBOCO_AGENT_AUTH_REQUIRED=true — secure mode does not extend to it. It is read-only (it carries system events like rate-limit lifecycle and usage snapshots; it accepts nothing from the client), so the exposure is limited to a reader seeing system telemetry. It is, however, one more reason the system must sit on a trusted network: anyone who can open that socket can watch the operator stream.

Next

API authentication for the header/token contract, Cloud auth for the CEO login session, the gateway for how agent capability is constrained, or back to common issues.

llms.txt