Skip to content

Commit 4d4ed61

Browse files
Stop a Bot's shell reading the deployment's keys from a neighbouring process (#552)
Co-authored-by: David McKay <david@copilotkit.ai>
1 parent 0613d6c commit 4d4ed61

7 files changed

Lines changed: 73 additions & 9 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,21 @@ jobs:
418418
docker logs openbot-ci
419419
exit 1
420420
}
421+
- name: A Bot's shell cannot reach the deployment secrets
422+
run: |
423+
set -uo pipefail
424+
if docker exec -u pwuser openbot-ci env -i PATH=/usr/bin:/bin sh -c '
425+
for e in /proc/[0-9]*/environ; do tr "\0" "\n" < "$e" 2>/dev/null; done
426+
' | grep -q "^KEY_ENCRYPTION_KEY="; then
427+
echo "::error::A pwuser shell reads KEY_ENCRYPTION_KEY from a sibling process /proc/<pid>/environ."
428+
exit 1
429+
fi
430+
if docker exec -u pwuser openbot-ci env -i PATH=/usr/bin:/bin sh -c \
431+
'cat /run/s6/container_environment/KEY_ENCRYPTION_KEY 2>/dev/null' | grep -q .; then
432+
echo "::error::A pwuser shell reads KEY_ENCRYPTION_KEY from /run/s6/container_environment."
433+
exit 1
434+
fi
435+
echo "pwuser cannot reach KEY_ENCRYPTION_KEY"
421436
- if: always()
422437
run: docker rm -f openbot-ci >/dev/null 2>&1 || true
423438

‎CHANGELOG.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.
88

99
## Unreleased
1010

11+
### A Bot's shell can no longer read the deployment's keys from a neighbouring process
12+
13+
In the all-in-one image the API and the browser ran under one account, and a Bot's shell — a child
14+
of the browser — could read a same-account process's environment through `/proc`, whatever its own
15+
environment had been scrubbed to. One allowed `computer_run_command` returned `KEY_ENCRYPTION_KEY`,
16+
the session-signing secret and the database password, none of it on the audit trail. The API and its
17+
migrations now run as their own account, so the kernel refuses that read; the browser is handed only
18+
the variables it needs, so its own environment carries none of those keys; and the files under
19+
`/run/s6/container_environment` are closed to the shell. A deployment that runs each Bot in its own
20+
sandboxed computer, as the documentation asks for, was never exposed to this.
21+
1122
## 0.0.11
1223

1324
### The LlamaIndex Bot answers with the model the setup screen chose

‎Dockerfile‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
3636
&& ln -s bun /usr/local/bin/bunx \
3737
&& bunx --bun "playwright@${PLAYWRIGHT_VERSION}" install --with-deps chromium \
3838
&& rm -rf /root/.cache /tmp/* /var/lib/apt/lists/* \
39-
&& useradd --create-home --shell /bin/bash pwuser
39+
&& useradd --create-home --shell /bin/bash pwuser \
40+
&& useradd --create-home --shell /usr/sbin/nologin apiuser
4041

4142

4243
FROM base AS deps
@@ -215,9 +216,9 @@ ENV AGENT_COMPUTER_URL=http://127.0.0.1:4100
215216
# NOTHING THAT MATTERS RUNS AS ROOT.
216217
#
217218
# s6 stays root because that is the only way it can drop each service to a different user, and they
218-
# genuinely differ: the browser and API run as `pwuser`, the database as `postgres`. One shared
219-
# account would put the process that renders the open internet in the same skin as the one holding
220-
# the audit trail.
219+
# genuinely differ: the browser and the Bot's shell run as `pwuser`, the API and migrations as
220+
# `apiuser`, the database as `postgres`. One shared account would put the process that renders the
221+
# open internet in the same skin as the one holding the audit trail.
221222
#
222223
# This matters more than usual here. Chromium is launched with `--no-sandbox` unless the host can
223224
# support its sandbox, and with that flag the process user IS the boundary, so root would mean a

‎docker/s6/s6-rc.d/api/run‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
# The API, and the app it serves. Started after the browser so a Bot's first action does not race a
33
# computer that is still coming up.
44
cd /app/server
5-
exec s6-setuidgid pwuser /usr/local/bin/bun src/index.ts
5+
export HOME=/home/apiuser
6+
exec s6-setuidgid apiuser /usr/local/bin/bun src/index.ts

‎docker/s6/s6-rc.d/computer/run‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,39 @@ if [ "${EMBEDDED_COMPUTER:-on}" != "on" ]; then
1717
exec /bin/true
1818
fi
1919
cd /app/agent-computer
20-
export PORT=4100
21-
export WORKSPACE_DIR=/workspace
22-
exec s6-setuidgid pwuser /usr/local/bin/bun src/index.ts
20+
21+
set --
22+
for name in \
23+
PATH LANG LANGUAGE TERM TERMINFO COLORTERM DISPLAY \
24+
HTTP_PROXY HTTPS_PROXY NO_PROXY ALL_PROXY FTP_PROXY \
25+
http_proxy https_proxy no_proxy all_proxy ftp_proxy \
26+
BUN_INSTALL PLAYWRIGHT_BROWSERS_PATH SPIFFE_ENDPOINT_SOCKET \
27+
COMPUTER_TOKEN COMPUTER_BOT_ID COMPUTER_BROWSER_MODE COMPUTER_MAX_BROWSERS \
28+
COMPUTER_BROWSER_IDLE_MS COMPUTER_SANDBOX COMPUTER_SHELL_ENV COMPUTER_STOPPED \
29+
ACTION_TIMEOUT_MS NAVIGATION_TIMEOUT_MS AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS
30+
do
31+
eval "isset=\${$name+x}"
32+
[ -n "${isset:-}" ] && eval "set -- \"\$@\" \"$name=\${$name}\""
33+
done
34+
for name in $(printenv | sed -n 's/=.*//p' | grep -E '^(LC_[A-Za-z0-9_]+|EGRESS_PROXY_[A-Za-z0-9_]+)$' || true); do
35+
eval "set -- \"\$@\" \"$name=\${$name}\""
36+
done
37+
if [ -n "${COMPUTER_SHELL_ENV:-}" ]; then
38+
oldifs=$IFS
39+
IFS=,
40+
for name in $COMPUTER_SHELL_ENV; do
41+
IFS=$oldifs
42+
name=$(printf %s "$name" | tr -d '[:space:]')
43+
case "$name" in
44+
'' | *[!A-Za-z0-9_]*) IFS=, ; continue ;;
45+
esac
46+
eval "isset=\${$name+x}"
47+
[ -n "${isset:-}" ] && eval "set -- \"\$@\" \"$name=\${$name}\""
48+
IFS=,
49+
done
50+
IFS=$oldifs
51+
fi
52+
53+
exec s6-setuidgid pwuser env -i "$@" \
54+
HOME=/home/pwuser PORT=4100 WORKSPACE_DIR=/workspace PROFILES_DIR="${PROFILES_DIR:-/profiles}" \
55+
/usr/local/bin/bun src/index.ts

‎docker/s6/scripts/computer-token.sh‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ if [ -z "${COMPUTER_TOKEN:-}" ]; then
1212
head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' \
1313
> /run/s6/container_environment/COMPUTER_TOKEN
1414
fi
15+
16+
chmod 0700 /run/s6/container_environment

‎docker/s6/scripts/migrate.sh‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
set -eu
99
[ "${EMBEDDED_POSTGRES:-off}" = "on" ] || exit 0
1010
cd /app/server
11+
export HOME=/home/apiuser
1112
# `scripts/migrate.ts`, not `drizzle-kit`. The CLI is a development dependency and needs esbuild to
1213
# read its TypeScript config, which `bun install --production` leaves out of this image: asked to
1314
# migrate here it exits 1 without printing why, and the container comes up against an empty database.
14-
exec s6-setuidgid pwuser /usr/local/bin/bun scripts/migrate.ts
15+
exec s6-setuidgid apiuser /usr/local/bin/bun scripts/migrate.ts

0 commit comments

Comments
 (0)