Require a token on every call from the server to a Bot - #52
Conversation
|
Reviewed and driven in a browser. The approach is right and the details that matter are correct: constant-time comparison rather than Checked it against current practice too. mTLS/SPIFFE is the 2026 recommendation for service-to-service, but SPIRE is an optional add-on here for the supervisor and computers and appears nowhere in I pushed two commits on top rather than asking you to, since a release is going out. The quick start could not start. The ports stayed on every interface, which your description names as the reason this mattered. Verified rather than assumed: from an empty token the script writes 44 characters and one line; the Bots then publish on Thank you for finding this one. |
Two things the token boundary needs to be usable and to be worth having. `MANAGED_AGENT_TOKEN` is required and ships empty in `.env.example`, so a fresh clone doing `cp .env.example .env && bash scripts/start.sh` refused to start with "MANAGED_AGENT_TOKEN must be configured". That is step four of the quick start, and the first thing a stranger does. `start.sh` already supplies the two neighbouring secrets; this one is generated and written back to `.env` rather than defaulted to a fixed string, because a well-known token from a public repository is no boundary at all, and because the server and the Bot are separate processes that have to agree on it across restarts. The Bot ports were published on every interface, which this PR's own description names as the reason the hole mattered. `agent-computer` was already bound to loopback, so the two Bots were the exception rather than the rule. Binding them means somebody has to be on the machine before the token is even the thing standing in their way. Driven rather than reasoned about: from an empty token, the script generates 44 characters and writes one line back; the Bots then publish on 127.0.0.1 only, answer 401 with no token and with a wrong one, and 200 with the right one.
`MANAGED_AGENT_TOKEN` is required, so the container refuses to start without it and the check waited 150 seconds for an answer that was never coming. The job could not have been updated in the original commit: this branch predates the check, and the workflow that ran came from the merge commit rather than from here. Rebasing onto main brings it into view. Reproduced with the job's own command: answers on /api/capabilities in four seconds, nothing respawning after fifteen.
141f591 to
e6e3ccb
Compare
|
Thanks for the review and the two follow-up commits. Generating the token rather than defaulting to a fixed string is the right call. I had considered a placeholder for start.sh and did not settle on one, and it did not click that its two loopback neighbours already generate theirs. Now all three follow the same pattern. Binding the Bots to 127.0.0.1 is the better half of that story: the token stops being the only thing standing between the port and the network. The SPIFFE/mTLS note is useful context. If the SPIRE work later reaches into Glad it landed before the release. |
The problem
agent-botandagent-langgraphservePOST /ag-uiwithout any authentication. Any process that can reach the Bot's port issues an AG-UI run against the deployment's model credential;docker-compose.yml:162binds${BOT_PORT:-4200}:4200on0.0.0.0, so on the shipped configuration that is any process on the host's network and any container on the compose network.MANAGED_AGENT_TOKENis not read anywhere in either Bot, andserver/src/agents/runtime-agents.tsattaches no authentication header to its own calls, so the boundary is absent on both sides rather than misconfigured.agent-computerandsupervisorboth require a shared secret at boot and validate it on every request; the two Bots did not.This is the opposite direction to #34, which added per-agent callback tokens for the Bot→server path. That change does not cover requests from the server into a Bot's
/ag-ui, which is what this PR closes.Closes #50.
The approach
A single header,
x-openbot-agent-token, on every call from the server to a managed Bot. A newshared/agent-authorisation.tsholds a constant-timematchesTokenand ahasManagedAgentTokenrequest helper, kept inshared/so the two Bots and the server all read the same rule rather than three subtly different ones. Both Bots refuse to start whenMANAGED_AGENT_TOKENis unset, no silent-open dev fallback because the process holds a model credential and an unauthenticated port on it is worth failing loudly for, and reject/ag-uiwith 401 when the header is missing or wrong.GET /healthstays open so an orchestrator can check readiness without holding the token.The server side matches.
server/src/config.tsrequiresMANAGED_AGENT_TOKENat boot alongside the other secrets.server/src/agents/runtime-agents.tsattaches the header only on the agent whose endpoint equalsconfig.managedAgentAgUiUrl, so customer-owned AG-UI endpoints never see it and a token intended for the built-in Bot is not leaked to a third party's server on the first customer registration.docker-compose.ymlpassesMANAGED_AGENT_TOKENthrough to both Bots;.env.example,README.md, anddocs/configuration.mddocument the new required variable.What is not covered
agent-computerandsupervisorwere already token-guarded and are unchanged.Merge notes
MANAGED_AGENT_TOKEN. Existing deployments that upgrade without setting it get a loud refuse-to-start on both the server and each Bot rather than a silent regression.managedAgentToken: string;loadConfigtest fixtures are updated.createRuntimeAgentLoadergains an optional trailingmanagedAgentparameter; existing callers that passundefinedkeep their old behaviour but issue no header, so a self-hosted deployment that runs a custom Bot in place of the managed one needs to opt in.Verification
All gates from a clean tree at
c81d9b1.bun run format:check: no fixesbun run lint: 25 pre-existingnoTemplateCurlyInStringwarnings, none newbun run typecheck: app, server, worker all exit 0bun run test: 686 pass, 5 skip, 0 fail across 79 files, 1701 assertionsbun run build: exit 0Live against a rebuilt
openbot-agent-botcontainer (docker compose up -d --build --force-recreate agent-bot):POST /ag-ui, no headersUnauthorized.POST /ag-ui,x-openbot-agent-token: wrongPOST /ag-ui,x-openbot-agent-token: $MANAGED_AGENT_TOKENRUN_STARTEDGET /healthNew tests:
shared/agent-authorisation.test.ts(2). Existing tests updated:server/tests/config.test.ts(required-token assertion),server/tests/runtime-agents.integration.test.ts(managed endpoint receives header, customer endpoint does not).