Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
b7ab0e8
docs: add design spec for Docker sandbox backend
radim10 Sep 23, 2026
555c8e8
feat(agent): add sandbox profile with Docker backend support and test…
radim10 Sep 23, 2026
6fe2dd8
feat(agent): implement Docker sandbox backend with configuration and …
radim10 Sep 23, 2026
24939b4
feat(agent): add Dockerfile for agent sandbox environment
radim10 Sep 23, 2026
4404e20
feat: enhance container management and git identity handling in Docke…
radim10 Sep 23, 2026
333f4c9
refactor: switch to official Node.js LTS image in Dockerfile for agen…
radim10 Sep 23, 2026
61b98ed
docs: expand README and agent profiles documentation for Docker sandb…
radim10 Sep 23, 2026
e337e74
feat(agent): add CLI option for Docker sandbox backend override and i…
radim10 Sep 23, 2026
9ed87aa
feat(agent): implement network namespace holder for Docker sandbox to…
radim10 Sep 23, 2026
a344ba8
feat(agent): add iptables support in Dockerfile for network namespace…
radim10 Sep 23, 2026
7529600
feat(agent): add jq package to Dockerfile for enhanced JSON processin…
radim10 Sep 23, 2026
a80c7b4
feat(agent): add firewall verification checks in Docker sandbox to en…
radim10 Sep 24, 2026
cbca680
feat(agent): update Dockerfile to install Claude Code and Codex via o…
radim10 Sep 24, 2026
000db83
feat(agent): enhance Docker sandbox command execution by forwarding T…
radim10 Sep 24, 2026
59d282f
feat(agent): add support for custom Docker images and Dockerfiles in …
radim10 Sep 24, 2026
76f99cf
docs(agent-profiles): update egress hosts for Claude and Codex profil…
radim10 Sep 24, 2026
791881b
docs: enhance README and agent profiles documentation for Docker sand…
radim10 Sep 24, 2026
83452d6
docs: expand sandboxing documentation to clarify Docker backend featu…
radim10 Sep 24, 2026
40fa69a
feat(agent): refine Docker sandbox startup process with improved spin…
radim10 Sep 24, 2026
e52247f
refactor(agent): update spinner messages for Docker sandbox preparati…
radim10 Sep 24, 2026
7d7b614
feat(agent): introduce Docker command for managing sandbox resources,…
radim10 Sep 24, 2026
c3bf2b9
feat(agent): add Docker commands for status listing and image buildin…
radim10 Sep 24, 2026
5c24e20
feat(agent): add profile source option for Docker build command to su…
radim10 Sep 24, 2026
c92f92e
docs(agent): expand documentation on Docker cleanup and image managem…
radim10 Sep 24, 2026
2801f5b
feat(agent): enhance Dockerfile for agent-sandbox by adding Python 3 …
radim10 Sep 24, 2026
c522e94
docs(sandboxing): update documentation to include Python 3 and pip in…
radim10 Sep 24, 2026
c21979c
feat(agent): add 'doctor' command to check Docker sandbox readiness, …
radim10 Sep 24, 2026
3ddc6de
feat(agent): enhance Docker build and cleanup commands with raw outpu…
radim10 Sep 25, 2026
bf9438e
feat(agent): implement Docker-specific runtime checks for profiles, e…
radim10 Sep 25, 2026
e06a1d4
docs: update README and agent profiles to recommend Docker backend fo…
radim10 Sep 25, 2026
08d95cd
feat(agent): introduce resource limits for Docker sandbox profiles, a…
radim10 Sep 25, 2026
eaba720
feat(agent): enforce process management in Docker sandbox by always u…
radim10 Sep 25, 2026
637a8bb
feat(agent): enhance Docker sandbox functionality with improved platf…
radim10 Sep 25, 2026
883dfec
docs(sandboxing): clarify network namespace handling and DNS configur…
radim10 Sep 25, 2026
6bf314d
feat(agent): replace /dev/null with an empty regular file for denied …
radim10 Sep 25, 2026
6ee046a
refractor(agent): restructure agent module by consolidating handlers
radim10 Sep 25, 2026
c46042c
feat(agent): add platform-specific user flag handling for Docker sand…
radim10 Sep 25, 2026
e50aa46
feat(tests): add function to ensure default sandbox image is built fo…
radim10 Sep 25, 2026
d66c410
fix(tests): update SSL_CERT_FILE path handling in Docker tests to ens…
radim10 Sep 25, 2026
3e5e599
fix(tests): update path handling in agent profile tests for cross-pla…
radim10 Sep 25, 2026
466105f
chore(.gitignore): add .serena directory and docs/superpowers/ to ign…
radim10 Sep 25, 2026
fa8065d
chore(docs): remove outdated Docker sandbox backend design document
radim10 Sep 25, 2026
ed4f952
docs: update Docker sandbox backend references and remove 'experiment…
radim10 Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.env
.env.*
.stashbase/*
.serena/*
.serena/*
docs/superpowers/
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Stashbase is an open-source access layer that gives coding agents the access the
- [How the Agent Proxy Works](#how-the-agent-proxy-works)
- [Profile Syntax and Configuration](#profile-syntax-and-configuration)
- [Filesystem and Network Containment](#filesystem-and-network-containment)
- [Docker Sandbox Backend](#docker-sandbox-backend)
- [Remote Agent Sessions](#remote-agent-sessions)
- [MCP Tools Authorization](#mcp-tools-authorization)
- [Audit Logs and Session Revocation](#audit-logs-and-session-revocation)
Expand Down Expand Up @@ -231,7 +232,7 @@ deny_write = [".git", "~/.ssh", "~/.aws"]

On macOS, Stashbase wraps the agent in Seatbelt, which enforces filesystem rules. On Linux and WSL2, it uses `systemd-run --user` with cgroup IP rules, or falls back to `bubblewrap` for namespace isolation. Windows native is not implemented; use WSL2 instead.

Denied reads return `/dev/null`; denied writes go to an empty overlay. Existing file descriptors and data already in memory are not affected. These are policy-only; these profiles do not require secrets.
Denied reads see empty content (a genuine empty regular file, not `/dev/null` — that's a character device, which confuses tooling that expects a normal file at that path); denied writes go to an empty overlay. Existing file descriptors and data already in memory are not affected. These are policy-only; these profiles do not require secrets.

#### Network containment

Expand All @@ -241,6 +242,27 @@ On macOS, this uses the deprecated `sandbox-exec` utility. On Linux and WSL2, it

This is network containment only, not filesystem, process-memory, or kernel isolation.

**If Docker is available, prefer the Docker sandbox backend below over the native one** — it's meaningfully stronger: filesystem access is allow-list rather than deny-list (nothing outside the working directory is visible at all, instead of specific paths being blocked), network egress is enforced at the network layer rather than relying on the agent to honor its proxy environment variables, and it works identically across macOS, Linux, and Windows (via Docker Desktop) instead of needing platform-specific mechanisms with a Windows gap. The native backend remains the default for now since it needs nothing beyond the CLI itself, but Docker is the recommended choice whenever it's an option.

### Docker Sandbox Backend

The recommended backend when Docker is available: the agent runs inside a Docker container instead of a same-host sandboxed process, with allow-list filesystem access and a network-layer firewall (enforced even against an agent that deliberately ignores its proxy env vars).

```toml
[sandbox]
backend = "docker"
```

```bash
stashbase agent run --profile coding -- claude
```

Or override the profile's choice for one invocation without editing the file: `--docker-sandbox true|false`, per-run image overrides with `--docker-image <ref>` / `--docker-dockerfile <path>`, and resource caps with `--docker-memory <value>` / `--docker-cpus <value>` (also settable per profile via `[sandbox] memory`/`cpus`; no cap by default).

Claude Code and Codex are pre-installed in the default sandbox image; a profile can also run its own image or Dockerfile instead (`[sandbox] image`/`dockerfile`) to add other tools, without loosening any of the sandbox constraints themselves.

See **[docs/sandboxing.md](docs/sandboxing.md)** for the full picture: how the network firewall is enforced, custom images, git identity forwarding, login persistence across images, Codex/Claude Code OAuth quirks, and current limitations.

### Remote Agent Sessions

Use `--remote` to run with credentials managed entirely in the Stashbase control plane. Profiles can use either application secrets with `[secrets]` (requires `project` and `environment`) or user-specific `[personal_credentials]` (no Stashbase API key required):
Expand Down
78 changes: 78 additions & 0 deletions docker/agent-sandbox/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Node.js is still needed here even though Claude Code and Codex are
# installed as native binaries below (neither invokes Node itself at
# runtime): MCP servers agents commonly connect to are typically run via
# `npx`, so `npm`/`node` need to be on PATH for those to work. This is a
# Debian base underneath (bookworm-slim variant), so apt-get below works
# the same as it would on `debian:bookworm-slim`.
FROM node:22-bookworm-slim

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
gh \
bubblewrap \
iptables \
jq \
dnsutils \
unzip \
less \
procps \
python3 \
python3-pip \
python3-venv \
&& rm -rf /var/lib/apt/lists/*

# Debian's system pip refuses a bare `pip install` (PEP 668,
# "externally-managed-environment") to protect the OS's own Python install
# from being clobbered by unrelated packages. That protection matters far
# less for an ephemeral sandbox container than it does on a real host — the
# base image itself is never mutated at runtime, so there's nothing lasting
# to corrupt — so it's relaxed here for a working `pip install` out of the
# box. `python3-venv` is still included for anyone who wants an isolated
# environment anyway.
ENV PIP_BREAK_SYSTEM_PACKAGES=1

# Claude Code and Codex are installed via their own official native
# installers rather than `npm install -g` — both projects document this as
# the recommended method: it installs a self-contained platform binary
# (no Node runtime involved for the tool itself, unlike the npm package
# which just wraps the same binary), and pulls from Anthropic's/OpenAI's
# own signed release infrastructure rather than the npm registry.
#
# Both installers default to installing under $HOME (`/root` during this
# build) and only add `~/.local/bin` to *root's* shell profile — neither
# knows this container will later run as an arbitrary uid (see the --user
# flag set on Linux in docker_run_command). So: make the installed trees
# world-readable/executable (`chmod -R a+rX`, never +w — nothing here
# should be writable by the sandboxed process) and symlink both into
# `/usr/local/bin`, which is on PATH for every user by default. Verified
# directly that both `claude --version` and `codex --version` work when
# invoked as a non-root uid with this layout, not just as root.
#
# Codex ships helper binaries (a code-mode host, voice tools) alongside
# its main executable, so its install tree is kept intact and symlinked
# rather than copying a single binary out the way Claude Code's simpler,
# single-file layout would allow.
RUN curl -fsSL https://claude.ai/install.sh | bash \
&& curl -fsSL https://chatgpt.com/codex/install.sh | sh \
&& chmod 755 /root \
&& chmod -R a+rX /root/.local /root/.codex \
&& ln -s /root/.local/bin/claude /usr/local/bin/claude \
&& ln -s /root/.local/bin/codex /usr/local/bin/codex

# A persistent volume is mounted here (see docker_sandbox.rs) so login
# state (e.g. Claude Code's ~/.claude, ~/.claude.json) survives across
# runs instead of vanishing with each --rm'd container. World-writable
# because the container may run as an arbitrary host uid (see the --user
# flag set on Linux in docker_run_command) with no matching passwd entry,
# so a named volume freshly created by Docker would otherwise be
# root-owned and unwritable to that uid.
RUN mkdir -p /home/agent && chmod 777 /home/agent

# `iptables` above is used by the short-lived network-namespace-holder
# container (see start_netns_holder in docker_sandbox.rs), which uses this
# same image — the actual agent container never runs iptables itself and
# holds no networking capabilities at all.
WORKDIR /workspace
13 changes: 6 additions & 7 deletions docs/agent-profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ paths = ["/user", "/repos/*"]

The child process receives `GH_TOKEN` as a placeholder. When it makes a matching HTTP request (GET to `/user` or `/repos/*` on `api.github.com`), the proxy injects the real token. Any unmatched request is blocked.

## Filesystem and Network Restrictions
## Filesystem, Network Restrictions, and Sandbox Backends

Restrict what the child can read or write:

Expand All @@ -63,12 +63,11 @@ deny_read = ["~/.ssh", "~/.aws"]
deny_write = ["~/.git"]
```

Paths use explicit prefixes: `~` for home, relative paths for the current directory. Enforcement uses platform-native mechanisms:
- **macOS**: Seatbelt sandbox
- **Linux**: `systemd-run` or `bubblewrap` (automatic fallback)
- **Unsupported platforms**: Validation fails closed; the run does not proceed
Paths use explicit prefixes: `~` for home, relative paths for the current directory.

Existing file descriptors and data already in process memory remain unrestricted.
By default, enforcement uses the platform-native mechanism (Seatbelt on macOS, `systemd-run`/`bubblewrap` on Linux). **If Docker is available, prefer `[sandbox] backend = "docker"` instead** — it's meaningfully stronger (allow-list filesystem access, a real network-layer firewall, and it works on Windows too, unlike the native backend).

See **[Sandboxing](sandboxing.md)** for the full picture: both backends, how the Docker backend's network firewall is enforced, custom images, git identity forwarding, login persistence, and Codex/Claude Code OAuth quirks.

## Network Access and HTTP Rules

Expand Down Expand Up @@ -205,7 +204,7 @@ The proxy is HTTP/HTTPS only and designed for standard developer tools. It does
- Request-body or query-parameter injection (credentials are header-only)
- Process-level isolation (same-user processes can still access broader system credentials)

For complete network isolation, use a container or VM.
For stronger filesystem and network isolation than the native backend provides, see [Sandboxing](sandboxing.md).

## Full Reference

Expand Down
8 changes: 6 additions & 2 deletions docs/agent-profiles/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Claude Code can access external tools and APIs through agent profiles. For examp

```toml
# .stashbase/agents/claude.toml
egress_hosts = ["api.anthropic.com", "mcp.linear.app"]
egress_hosts = ["api.anthropic.com", "platform.claude.com", "mcp.linear.app"]

[secrets]
project = "my-project"
Expand Down Expand Up @@ -37,13 +37,15 @@ stashbase agent run --profile claude -- claude

Claude Code receives only the Linear credential placeholder and can use allowed MCP tools through the proxy.

`platform.claude.com` is required alongside `api.anthropic.com` if you're logged in via OAuth (`/login` in Claude Code) rather than an API key — Claude Code's OAuth login and silent token refresh both go through `platform.claude.com`, a different host than the one used for actual model requests. Without it, login itself fails with "OAuth error: proxy refused the connection", or — if you were already logged in before restricting egress — the session works until the access token's next refresh is silently blocked, then fails hours later with "OAuth access token has expired."

## Codex

Codex needs GitHub access for repository operations and OpenAI for completions:

```toml
# .stashbase/agents/codex.toml
egress_hosts = ["api.openai.com", "chatgpt.com", "api.github.com"]
egress_hosts = ["api.openai.com", "chatgpt.com", "auth.openai.com", "api.github.com"]
allow_hooks = ["dependency_check"]

[secrets]
Expand Down Expand Up @@ -81,6 +83,8 @@ Run:
stashbase agent run --profile codex -- codex
```

`auth.openai.com` is required for `codex login --device-auth` (and its silent token refresh) — it's a different host than `api.openai.com`, which only serves completions. Without it, device-code login fails with an error like "failed to request device code: error sending request for url (https://auth.openai.com/api/accounts/deviceauth/usercode)".

## HTTP MCP Server

MCP servers over HTTP (like Linear) use a separate credential binding and tool allowlist:
Expand Down
Loading
Loading