Letting an agent run shell commands: use a sandbox or refuse
A shell tool is the most powerful thing you can hand to an agent. Wrap every command in an operating-system sandbox, and if no sandbox is available, refuse to run instead of running unprotected. Make the unsafe switch explicit, loud and off by default.
π― For A developer who gives an agent a shell or code execution tool
- What a minimal sandbox for agent commands isolates
- Why the default must be refusal when no sandbox exists
- How to keep an escape hatch from becoming the normal path
π Why the shell is different
Most tools do one thing. A shell tool does anything the account can do: read keys, delete a directory, open a network connection, start a background process. An agent that can run commands is only as safe as the box around those commands, because its instructions come from text that may include text it should not trust.
π§± What our sandbox does
Our agent shell tool never runs a command directly. It wraps it with an isolation program, bwrap first and firejail as the alternative. With bwrap the wrapper does the following.
- It starts with an empty root file system in memory, then adds only what the command needs, mounted read-only: the system programs and libraries.
- It makes only the allowed work folders writable, and creates the working directory inside them.
- It isolates the process list, inter-process communication and host name from the rest of the machine.
- It cuts the network unless the call explicitly allows it.
- It dies with its parent, so a stuck command does not outlive the agent.
The list of allowed work folders is configured, and a path outside it is refused before the wrapper starts.
π« The important part: no sandbox, no run
If neither isolation program is installed, the tool does not fall back to running the command bare. It returns an error named no_sandbox_available and does nothing. This is the decision recorded in our design note of 23 May 2026: a safe default that fails closed.
An escape hatch exists, because a developer laptop may have no isolation program: an environment variable that allows commands without isolation. Using it writes a warning on each run, and every call, allowed or refused, is written to an audit record with the isolator used. Keep the switch out of service definitions. The point is the shape: the dangerous mode needs a deliberate, visible act, and the safe mode needs none.
π οΈ Practice: wrap your own shell tool
- Decide which folders a command may write to, and write them down in one setting.
- Install an isolation program on every host that runs agents. Check with a script that prints which one it found.
- Write the wrapper so that the command list goes into the isolation program as arguments, never as a string for a second shell.
- Cut the network by default. Add a per-call flag for the few commands that need it, and log each use.
- Make the tool return a clear error when no isolation program is found. Do not add a silent fallback.
- Add one environment switch for unsafe mode. Make it print a warning every time and keep it out of production units.
- Write tests: a write outside the allowed folders fails, a network call fails, a missing isolation program refuses.
β Check yourself
- Run a command that writes to your home directory. It fails.
- Run a command that opens a network connection. It fails unless the call allowed the network.
- Remove the isolation program on a test host. The tool refuses with a named error.
- Search your service definitions for the unsafe switch. It is not there.
π§ When this does NOT work
- Escapes through allowed folders. Whatever you make writable, the agent can change. Do not put the repository root, your home directory or a folder with keys in the list.
- Network on. If a command needs the network, it can send out anything it can read. Allow it for named commands only.
- Kernel-level bugs. A sandbox reduces risk and is not a guarantee. Keep the host patched and do not run it as the most privileged account.
- Slow commands. Isolation adds start-up time. Do not skip it to save milliseconds.
- Containers hiding the problem. If the agent already runs in a throw-away container, a second layer may add little. Decide that explicitly and write it down.
π Evidence
On 3 November 2026, 21:53 UTC the shell tool listed two isolation programs in its code. On the host that produced this page, bwrap was installed and firejail was not installed. The shell sandbox test file held 14 tests and the general tools test file 11.
| What we measured | Isolation options in the tool code, their presence on this host, and test counts |
| When | 3 November 2026, 21:53 UTC |
| Artifact | The shell tool module, its sandbox test file and the design note of 23 May 2026 in our private repository |
| Limit | Counts show that the behaviour is tested, not that no escape exists |
β‘οΈ Next
Read what an agent may touch in Permissions and data flow, and the decisions in Agent shell access: decision forks.
Sources
- Agent shell access: decision forks: choices for shell access.
- Permissions and data flow: what an agent may touch.
- MCP server security checklist: checks before connecting data.
- Recovery after a failed, stuck or uncertain run: when a command goes wrong.
π Article connections
Open the full knowledge graph βPublished article links. Select a node to open its article at the heading.
Article links Β· keyboard / text view
Same topic
- A vector base is a map, not a source: what we learned from retrieval
- When you cannot tell what a model call cost: freeze, then reconcile
- Splitting a big task for a cheap model: the 24,000-byte lesson
- Skills, MCP tools and rules: three ways to give an agent capabilities
- Eight shell and git mistakes our agents made, and the rule for each
- Several agents, one repository: ownership rules from real incidents
Next steps
Related reading
A vector base is a map, not a source: what we learned from retrieval
A vector base finds passages that are close in meaning to your question. It does not know which are true or current. Treat every hit as a pointer: open the file it names, check the hash, and take numbers from the artifact, never from the retrieved paragraph.
AI agents and MCP explainedWhen you cannot tell what a model call cost: freeze, then reconcile
Sometimes a model call times out and you do not know whether the provider charged you. Never record that as zero. Mark it uncertain, stop new paid calls, and let a named person reconcile it with the provider's statement. Refusals before generation are the exception.
AI agents and MCP explainedSplitting a big task for a cheap model: the 24,000-byte lesson
A cheap model does well on one small, well-defined job and badly on a big pile. We cap a task at 24,000 bytes and refuse larger ones before any model is called. Split by unit, one file or one country per task, and check the size before you start.
AI agents and MCP explainedSkills, MCP tools and rules: three ways to give an agent capabilities
Agents get abilities in different ways. A rule constrains what they do, a skill tells them how to do a job, and an MCP tool lets them call a function. Each has its own risk, so declare all three in a register and test that nothing exists unlisted.
π¬ Comments