Skip to content

Stop handing a Bot's shell the deployment's secrets - #69

Closed
davidmckayv wants to merge 1 commit into
mainfrom
fix/shell-env-allowlist
Closed

Stop handing a Bot's shell the deployment's secrets#69
davidmckayv wants to merge 1 commit into
mainfrom
fix/shell-env-allowlist

Conversation

@davidmckayv

Copy link
Copy Markdown
Contributor

What this changes

A Bot's shell command ran with {...process.env}. That process requires COMPUTER_TOKEN, and in the
one-container image the same environment carries DATABASE_URL, KEY_ENCRYPTION_KEY, the licence and
the model key, because there is one environment and every service reads it.

COMPUTER_TOKEN is the only thing in front of the control surface on 4100, and the shell runs inside
the process that serves it. So one command could drive the browser over loopback with no policy
decision and no audit row: the gateway was optional for the one actor it exists to constrain. The
database URL and the encryption key together opened the credential vault and the audit trail meant to
record what happened.

A command now receives HOME, PATH, DEBIAN_FRONTEND and the locale. An allow-list, not a
deny-list, because the name worth removing is the one nobody thought of.

Where it runs

  • New state that outlives a request? None.
  • What happens on the second replica? Identical. One function of its arguments.
  • Anything serialised? No.
  • Anything fanned out to a browser? No.
  • New listener, port, or schedule? None.

Boundary and audit

  • No acting path changed. This removes a way around the acting path.
  • No refusal or failure behaviour changed.
  • Nothing new is trusted from the client.

Changelog

  • Entry under Unreleased.

Proof

Driven in the running app on :3010, through the Bot, not only unit tested. Asked General Assistant
to run env | cut -d= -f1 | sort before and after, same conversation, same command:

before:  COMPUTER_TOKEN HOME HOSTNAME LANG LC_ALL PATH PLAYWRIGHT_BROWSERS_PATH PORT PWD SHLVL WORKSPACE_DIR _
after:   DEBIAN_FRONTEND HOME LANG LC_ALL PATH PWD SHLVL _

COMPUTER_TOKEN gone. What remains is the allow-list plus the four bash sets itself. The command still
works: whoami and pwd returned, exit 0, in /workspace.

Driving it caught something a unit test could not: the first run still leaked, because the deployment
talks to the shared computer at AGENT_COMPUTER_URL rather than a per-Bot container, so the fix was
not in the path until that container was rebuilt. A green unit test would have read as done.

Also found and fixed in review: my first version of the test set DATABASE_URL on the real
process.env, which broke two unrelated tests that read one. It now asserts the whole variable set
using a marker name nothing else reads.

  • bun test 748 pass / 0 fail (745 baseline plus 3 new). Red/green verified both directions:
    reverting the fix fails 2 of the 3, restoring it passes all 3.
  • bun run typecheck exit 0, biome lint and biome format clean.

Not fixed here

Adjacent, same file, deliberately left for their own changes: bash -lc sources
/workspace/.bash_profile, which a Bot can write via computer_write_file, so a later command runs it
while the audit row records only the innocuous text. And output accumulates unbounded before it is
clamped. Say the word and I will take them.

A command ran with the whole environment of the process that started it. That
process needs COMPUTER_TOKEN to authenticate its own callers, and in the
one-container image the same environment carries DATABASE_URL,
KEY_ENCRYPTION_KEY, the licence and the model key, because there is one
environment and every service reads it.

So the Bot was handed the credential that exists to keep the Bot out.
COMPUTER_TOKEN is the only thing in front of the control surface on 4100, and
the shell runs inside the process that serves it, so one command could drive the
browser over loopback with no policy decision and no audit row. The gateway was
optional for the one actor it exists to constrain. The database URL and the
encryption key together opened the credential vault and the trail that is
supposed to record what happened.

A command now gets HOME, PATH, DEBIAN_FRONTEND and the locale. An allow-list
rather than a deny-list, because the name worth removing is the one nobody
thought of, and a variable added next year is covered without anybody
remembering this file. DEBIAN_FRONTEND is not incidental: apt-get otherwise
waits for an answer nobody is there to give, and the command times out looking
like a broken package rather than a prompt.

The test asserts the whole set rather than listing names to exclude, and runs a
real shell, because the question is what a process actually inherits and a mock
would only confirm what we believed it inherits.
@davidmckayv

Copy link
Copy Markdown
Contributor Author

Superseded by #68, which does this better and was opened first.

#68 covers everything here and more: the proxy variables (this PR would have broken apt-get behind a corporate proxy), the LC_* categories, TERMINFO/COLORTERM, and an operator escape hatch via COMPUTER_SHELL_ENV so passing a named secret is a decision rather than the default. Nine tests to this one's three, and it updates .env.example, the README and two docs pages that this left stale.

The one thing worth carrying over is DEBIAN_FRONTEND=noninteractive, which #68 does not set. I have pushed it there.

The verification from here also applies to #68 and I have added it to that PR: driven through the Bot on a running deployment, COMPUTER_TOKEN disappears from the command's environment and the command still works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant