Sandboxing a bot
Containers can be used for easy and effective isolation of agent harnesses from your development environment
Letting agent harnesses, like Pi or Claude Code run on your machine with your credentials is convenient but also very insecure. Limiting the bots by telling them 'thou shalt not' is pretty much as useless as it is with people, so some proper limitations to capabilities are warranted. If the bot cannot even see that drive over there, there is no way it can read or modify the files inside - at least until it helpfully breaks out of the box and grabs them anyway.
There are different ways to isolate a bot, ranging from giving them a full standalone VM that they can do anything they want with (e.g. exe.dev), to something like GH Copilot's sandbox mode which uses a softer approach of trying to control what is allowed and what not. I've found that for my workflow (CLI within the editor, one task at a time. Old fashioned, I know...) a container based approach hits a sweet spot between security, ease of use and not wasting too much resources.
Depending on the goal, a bot needs access to all kinds of things: source code and build/test tools, of course, but also things like a script runner, dev db and a query mechanism, web search, CLI tools, MCPs and more. That's a lot of tooling that can be both useful for the task at hand and detrimental to your machine - think things like supply chain attacks, secret leakage, misconfigured environment etc.
Luckily, sending a CLI-based bot to a containerized jail is a simple enough process:
- Define a container with a minimal image
- Add the harness and required configuration
- Add the tools you want to allow
- Map the environment variables / volumes / credentials
- Make sandboxing the default when invoking the harness
Step by step:
The container file
I use rootless docker for the container engine, but there are a lot of other options. The key thing is to keep the container as small as possible while providing the bot things it most commonly needs.
A sample:
FROM node:26-bookworm-slim
# Install base tools
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bash build-essential ca-certificates curl git ripgrep wget postgresql-client python3 libicu-dev
# harness
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent
# sample external tool - Allium CLI
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain stable
RUN /root/.cargo/bin/cargo install --locked allium-cli
WORKDIR /sandbox
ENTRYPOINT ["pi"]
That takes care of the base structure and tooling of the container. Next up - how to actually run it.
The initialization script
These are executable files like pi or copilot that set up the environment and start the sandbox with the harness in question. Adding these to PATH allows to use the tools exactly like normal.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
env_args=(
--env "CORTECS_KEY"
--env "PGHOST=postgres"
--env "HTTP_PROXY"
--env "HTTPS_PROXY"
--env "NO_PROXY"="localhost,127.0.0.1,postgres"
)
exec docker run \
--rm \
--network sandbox-net \
--tty \
--interactive \
--workdir "/sandbox" \
--entrypoint "pi"
--volume "${PWD}:/sandbox" \
--volume "${HOME}/work:/root/work" \
--volume "${HOME}/.dotnet/tools:/root/.dotnet/tools" \
--volume "${HOME}/.nuget:/root/.nuget" \
--volume "$HOME/.dotnet:/root/.dotnet" \
--env DOTNET_ROOT="/root/.dotnet" \
--env NUGET_PACKAGES="/root/.nuget/packages" \
--env PATH="$PATH:/root/.dotnet" \
--volume "${HOME}/.agents:/root/.agents" \
--volume "${HOME}/.agents/models.json:/root/.pi/agent/models.json:ro" \
--volume "${HOME}/.pi/agent/sessions:/root/.pi/agent/sessions" \
--env PI_AGENT_HOME="/root/.pi/agent" \
"${env_args[@]}" \
"pi" \
"$@"
A couple of things to note from the example:
- I share e.g. dotnet tools between the host and the sandbox. This reduces wasted compute and bandwidth as both sides don't have to download and manage the same assets. However, it does weaken the security boundary. Maybe I'll lock that down just to be sure, but for now, it's open.
- I've placed the sandbox in a separate 'sandbox-network' which allows the bot inside to talk to e.g. a postgresql database running in another container on the same network. This creates a clear distinction between resources the bot can access and those it cannot. The network itself has unrestricted Internet access.
After adding it to the PATH and running pi, the tool opens as normal and the current directory is shown as /sandbox. The bot can access the other mapped volumes from e.g. /root/.agents/skills. Only the selected environment variables are available, so in this example the bot can access Cortecs but does not have visibility into, for example, a GitHub Copilot token.
And as the host sees it as just pi, using it from inside the editor is no different than it would be 'normally'. Tightly integrated to the editor, no SSH shenanigans, but still isolated in a way that I feel pretty comfortable giving the bot full access without fear of it mangling anything too badly.
Currently my bots are allowed to install more tools inside the container, but a more cautious approach might be warranted. Wouldn't want one to start running a crypto miner... My finger is on the trigger, but for now, a little freedom is permitted.
I have published my setup which is a more complete version of the above. It includes additions like rebuilding the container and working behind corporate proxies. It's tailored for my personal use, so instead of using it as is, I'd recommend using it as inspiration for setting one up yourself
Or have a bot implement it's own jail. Poetic, is it not?
Thoughts, comments? Send me an email!