computer_run_command (#62) spawns the command with the computer process's own environment:
// agent-computer/src/shell.ts:81
env: { ...process.env, HOME: workspaceDir },
What that inherits depends on how the deployment is run, and the two shapes differ sharply.
Under docker-compose.yml, agent-computer is handed exactly one variable (docker-compose.yml:44), COMPUTER_TOKEN. Inheriting that is close to harmless.
Under the one-container image it is not. The computer runs as an s6 service whose run script starts #!/command/with-contenv sh (docker/s6/s6-rc.d/computer/run:1), and the comment there is explicit that this is deliberate: without it "s6 starts a service with none of the container's environment." The API beside it starts the same way (docker/s6/s6-rc.d/api/run:1), so both hold the container's environment, and docs/deployment.md:9-14 runs that container with --env-file .env. That file is the deployment's secrets: KEY_ENCRYPTION_KEY (.env.example:5), DATABASE_URL (:1), OPENAI_API_KEY (:77), INTELLIGENCE_API_KEY and COPILOTKIT_LICENSE_TOKEN (:49-50), COMPUTER_TOKEN (:119), SUPERVISOR_TOKEN (:195), AGENT_TOOL_TOKEN (:204). DATABASE_URL is in the image itself at Dockerfile:181, so a deployment on a managed database carries it too rather than only an embedded one.
So in the deployment shape the docs lead with, env is a one-word command that returns all of them.
It runs by default. DEFAULT_ACTION_POLICY is {mode:"enforce", deny:[], allow:["true"]} (server/src/computer/policy-store.ts:36), and /exec has no gate of its own (agent-computer/src/index.ts:760), correctly, since the gateway is what decides. The trail records the command text and deliberately not its output, so the row this leaves reads like any other command.
KEY_ENCRYPTION_KEY is the one that travels furthest. The database is reachable from that same container either way, on loopback with EMBEDDED_POSTGRES=on (docker/s6/s6-rc.d/postgres/run) or at whatever DATABASE_URL names. The key plus the database is every credential an administrator stored through /admin/credentials, which the README describes as "encrypted at rest, never returned by an API, and redacted from audit events." It is also a way to act with no audit row at all, against a gateway whose stated property is that "there is no path that acts without the record existing first."
The part I am less sure how you want handled
Dockerfile:134-137 already states the precondition for the shell, and states it better than I would:
BE CLEAR WHAT THIS COSTS. It means a Bot can become root inside its container. That is acceptable when the container is the Bot's alone and is contained from below, which is why per-Bot computers and gVisor are not optional extras next to this feature; they are what makes it sane. In a container shared between Bots, or one holding a database, a Bot with sudo can reach all of it.
Both disqualifying conditions are properties of the image that comment is in. The supervisor is deliberately not in it (Dockerfile:13, docs/deployment.md:36), so every Bot shares the one computer; and EMBEDDED_POSTGRES=on is a documented way to run it, so the container can hold the database. Passwordless sudo is granted at Dockerfile:140.
That makes scrubbing the environment a floor rather than the boundary: root in a shared container can read another process's environment whatever the shell was handed. I did not want to guess at the larger answer, whether the shell should be off unless a supervisor is configured, whether EMBEDDED_POSTGRES and a shell should refuse each other at boot the way a partial auth configuration already does, or whether this is understood and accepted for the trial shape the image is aimed at.
What I would send
A PR that stops the shell inheriting the environment, as an allow list rather than a deny list, because a deny list is the secrets that existed on the day it was written and the next variable added to a deployment is not on it. PATH, the locale and terminal names, and the proxy variables pass, because an apt-get behind a corporate proxy reaches nothing without them. COMPUTER_SHELL_ENV names anything else a deployment wants passed, read literally, so naming a secret there is an operator's decision rather than the default.
Happy to leave it there, or to take the larger question separately once you have said which way you want it.
computer_run_command(#62) spawns the command with the computer process's own environment:What that inherits depends on how the deployment is run, and the two shapes differ sharply.
Under
docker-compose.yml,agent-computeris handed exactly one variable (docker-compose.yml:44),COMPUTER_TOKEN. Inheriting that is close to harmless.Under the one-container image it is not. The computer runs as an s6 service whose run script starts
#!/command/with-contenv sh(docker/s6/s6-rc.d/computer/run:1), and the comment there is explicit that this is deliberate: without it "s6 starts a service with none of the container's environment." The API beside it starts the same way (docker/s6/s6-rc.d/api/run:1), so both hold the container's environment, anddocs/deployment.md:9-14runs that container with--env-file .env. That file is the deployment's secrets:KEY_ENCRYPTION_KEY(.env.example:5),DATABASE_URL(:1),OPENAI_API_KEY(:77),INTELLIGENCE_API_KEYandCOPILOTKIT_LICENSE_TOKEN(:49-50),COMPUTER_TOKEN(:119),SUPERVISOR_TOKEN(:195),AGENT_TOOL_TOKEN(:204).DATABASE_URLis in the image itself atDockerfile:181, so a deployment on a managed database carries it too rather than only an embedded one.So in the deployment shape the docs lead with,
envis a one-word command that returns all of them.It runs by default.
DEFAULT_ACTION_POLICYis{mode:"enforce", deny:[], allow:["true"]}(server/src/computer/policy-store.ts:36), and/exechas no gate of its own (agent-computer/src/index.ts:760), correctly, since the gateway is what decides. The trail records the command text and deliberately not its output, so the row this leaves reads like any other command.KEY_ENCRYPTION_KEYis the one that travels furthest. The database is reachable from that same container either way, on loopback withEMBEDDED_POSTGRES=on(docker/s6/s6-rc.d/postgres/run) or at whateverDATABASE_URLnames. The key plus the database is every credential an administrator stored through/admin/credentials, which the README describes as "encrypted at rest, never returned by an API, and redacted from audit events." It is also a way to act with no audit row at all, against a gateway whose stated property is that "there is no path that acts without the record existing first."The part I am less sure how you want handled
Dockerfile:134-137already states the precondition for the shell, and states it better than I would:Both disqualifying conditions are properties of the image that comment is in. The supervisor is deliberately not in it (
Dockerfile:13,docs/deployment.md:36), so every Bot shares the one computer; andEMBEDDED_POSTGRES=onis a documented way to run it, so the container can hold the database. Passwordless sudo is granted atDockerfile:140.That makes scrubbing the environment a floor rather than the boundary: root in a shared container can read another process's environment whatever the shell was handed. I did not want to guess at the larger answer, whether the shell should be off unless a supervisor is configured, whether
EMBEDDED_POSTGRESand a shell should refuse each other at boot the way a partial auth configuration already does, or whether this is understood and accepted for the trial shape the image is aimed at.What I would send
A PR that stops the shell inheriting the environment, as an allow list rather than a deny list, because a deny list is the secrets that existed on the day it was written and the next variable added to a deployment is not on it.
PATH, the locale and terminal names, and the proxy variables pass, because anapt-getbehind a corporate proxy reaches nothing without them.COMPUTER_SHELL_ENVnames anything else a deployment wants passed, read literally, so naming a secret there is an operator's decision rather than the default.Happy to leave it there, or to take the larger question separately once you have said which way you want it.