Skip to content

A Bot's shell is handed the deployment's environment, including the key that decrypts stored credentials #66

Description

@Hotragn

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions