Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,33 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.

## Unreleased

### A Bot's computer is no longer on the same network as the database

Compose declared no networks, so every service shared one and reached the others by service name.
One of those services is the container a Bot's shell runs in, and another is PostgreSQL, whose
username and password are in the same file. A shell reaches whatever its container reaches, so a Bot
could open `postgres:5432` and authenticate: the audit trail, the policy store and the agent tables,
from the one container whose job is to run what a Bot asks for. The role Compose creates is the
instance owner, so the trail's append-only trigger was no defence either, being something its owner
can drop.

PostgreSQL and `migrate`, the only service that reaches it by name, are now on a `data` network of
their own. Everything else stays where it was. Nothing changes for a deployment that runs the API
server on the host, which reaches the database through the published port and never used the shared
network for it. **A deployment that runs the server inside Compose has to join that service to both
networks**, which is the one place the two are meant to meet.

The published port is now on loopback, as every other port in that file already was. Taking the
database off the Bots' network removes the name, not the address: a container's default gateway is
the host, and a port published on every interface answers there. From inside the computer container,
the gateway on `5432` accepted a connection and began authenticating as `openbot` on `openbot`, with
the password in the same file. **A deployment that reached the database from another machine over
this port has to reach it another way**, which is what publishing it on every interface was doing.

This does not reach back in time. A deployment that has been running with the two on one network
should assume a Bot could have read or written the database, and look at the trail with that in
mind.

### Name the private addresses an agent may live at

Refusing `AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS` in production closed a hole and took something with
Expand Down
34 changes: 33 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
services:
postgres:
image: pgvector/pgvector:pg17
# Off the network the Bots are on, because a Bot's computer is on it and a Bot has a shell.
#
# With no `networks:` anywhere, Compose puts every service on one network with DNS by service
# name, so `agent-computer` could open `postgres:5432` and the credentials three lines below are
# in this file. That is the audit trail, the policy store and the agent tables, reachable from
# the one container whose whole job is to run what a Bot asks for. `agent-computer/src/shell.ts`
# says it plainly: isolation is the container's job, a shell can reach whatever the container
# can reach.
#
# `migrate` is the only service that reaches this by name, so the two of them are all that
# belongs here. Everything else that needs the database is outside this file: the API server
# runs on the host in local development and connects through the published port below.
environment:
POSTGRES_DB: openbot
POSTGRES_USER: openbot
POSTGRES_PASSWORD: openbot
ports:
- "${POSTGRES_PORT:-5432}:5432"
# Loopback, like every other port in this file, and for the reason the network split above
# exists. Published on every interface, this is reachable from the Bot's computer at the
# container's default gateway, which is the host: taking `postgres` off that network removes
# the name and leaves the address. Proven from inside `agent-computer`, where the gateway on
# :5432 answered and began authenticating as `openbot` on `openbot`, with the password in
# this file. The server on the host still reaches it, because that is what loopback is.
- "127.0.0.1:${POSTGRES_PORT:-5432}:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openbot -d openbot"]
interval: 5s
timeout: 5s
retries: 10
networks:
- data

migrate:
build:
Expand All @@ -26,6 +46,8 @@ services:
postgres:
condition: service_healthy
restart: "no"
networks:
- data

# The Bot's computer is long-lived so browser sessions remain signed in across turns.
agent-computer:
Expand Down Expand Up @@ -233,6 +255,16 @@ services:
timeout: 5s
retries: 5

networks:
# The database and the one service that reaches it by name. Nothing a Bot can reach is on it.
#
# A deployment that runs the API server inside Compose rather than on the host puts that service
# on both this and `default`, which is the one place the two are meant to meet.
data:
# Everything else, which is where `default` already put it. Named here only so that adding a
# service without a `networks:` key keeps landing beside the Bots rather than beside the database.
default:

volumes:
postgres-data:
agent-workspace:
Expand Down
2 changes: 2 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ startup.

`agent-computer` requires `COMPUTER_TOKEN` and permits only `/health` without it. Docker Compose binds it to `127.0.0.1:4100`.

Compose puts it on a different network from PostgreSQL. A Bot has a shell, and a shell reaches whatever its container reaches, so the database is on a `data` network carrying only itself and `migrate`, and everything else is on `default`. A deployment that runs the API server inside Compose rather than on the host joins it to both, which is the one place the two meet.

With `COMPUTER_SUPERVISOR_URL`, each Bot gets its own computer container, workspace volume, and browser profile. Without it, all Bots share `AGENT_COMPUTER_URL`.

A command on the computer inherits PATH, locale and terminal names, and the proxy variables, not the rest of the process environment. Userinfo is stripped from a proxy URL. `COMPUTER_SHELL_ENV` names anything else a deployment wants passed.
Expand Down