An OpenCode plugin that sandboxes agent-executed commands using @anthropic-ai/sandbox-runtime.
Every bash tool invocation is wrapped with OS-level filesystem and network restrictions — no containers, no VMs, just native OS sandboxing primitives.
| Platform | Mechanism |
|---|---|
| macOS | sandbox-exec (Seatbelt profiles) |
| Linux | bubblewrap (namespace isolation) |
| Windows | Not supported (commands pass through) |
// opencode.json
{
"plugin": ["opencode-sandbox"]
}The plugin is automatically installed from npm when OpenCode starts.
1. Install bubblewrap:
# Debian/Ubuntu
sudo apt install bubblewrap
# Fedora
sudo dnf install bubblewrap
# Arch
sudo pacman -S bubblewrap2. Ubuntu 24.04+ (AppArmor fix):
Ubuntu 24.04 and later restrict unprivileged user namespaces via AppArmor, which prevents bubblewrap from working. You need to enable the bwrap-userns-restrict AppArmor profile:
# Install the AppArmor profiles package
sudo apt install apparmor-profiles
# Create the symlink to enable the profile
sudo ln -s /etc/apparmor.d/bwrap-userns-restrict /etc/apparmor.d/force-complain/bwrap-userns-restrict
# Load the profile
sudo apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrictYou can verify bwrap works:
bwrap --ro-bind / / --dev /dev --proc /proc -- echo "sandbox works"Without this fix, bwrap will fail with loopback: Failed RTM_NEWADDR: Operation not permitted or setting up uid map: Permission denied.
When the agent runs a bash command, the sandbox enforces three layers of protection:
Commands can only write to the project directory and /tmp. Writing anywhere else returns "Read-only file system":
$ touch ~/some-file
touch: cannot touch '/home/user/some-file': Read-only file system
$ echo "data" > /etc/config
/usr/bin/bash: line 1: /etc/config: Read-only file system
Access to credential directories is blocked:
$ cat ~/.ssh/id_rsa
cat: /home/user/.ssh/id_rsa: Permission denied
Only approved domains are reachable. All other traffic is blocked via a local proxy:
$ curl https://evil.com
Connection blocked by network allowlist
$ curl https://registry.npmjs.org
(works — npmjs.org is in the default allowlist)
Filesystem (deny-read):
~/.ssh,~/.gnupg~/.aws/credentials,~/.config/gcloud~/.npmrc,~/.env
Filesystem (allow-write):
- Project directory
- Git worktree (validated — unsafe paths like
/are rejected) /tmp
Network (allow-only):
registry.npmjs.org,*.npmjs.orgregistry.yarnpkg.compypi.org,crates.iogithub.com,*.github.comgitlab.com,*.gitlab.comapi.openai.com,api.anthropic.com*.googleapis.com
Everything else is blocked by default.
// .opencode/sandbox.json
{
"filesystem": {
"denyRead": ["~/.ssh", "~/.aws/credentials"],
"allowWrite": [".", "/tmp", "/var/data"],
"denyWrite": [".env.production"]
},
"network": {
"allowedDomains": [
"registry.npmjs.org",
"github.com",
"*.github.com",
"api.openai.com",
"api.anthropic.com",
"my-internal-api.company.com"
],
"deniedDomains": ["malicious.example.com"]
}
}OPENCODE_SANDBOX_CONFIG='{"filesystem":{"denyRead":["~/.ssh"]},"network":{"allowedDomains":["github.com"]}}' opencodeOPENCODE_DISABLE_SANDBOX=1 opencodeOr in .opencode/sandbox.json:
{
"disabled": true
}The plugin uses two OpenCode hooks:
tool.execute.before— Intercepts bash commands and wraps them withSandboxManager.wrapWithSandbox()before executiontool.execute.after— Restores the original command in the UI (hides the bwrap wrapper)
Agent → bash tool → [plugin wraps command] → sandboxed execution → [plugin restores UI] → Agent
The AI model interprets sandbox errors (like "Read-only file system" or "Connection blocked") directly from command output — no additional annotation layer needed.
If anything goes wrong (sandbox init fails, wrapping fails, platform unsupported), commands run normally without sandbox. The plugin never breaks your workflow.
See CONTRIBUTING.md for development setup, architecture, and guidelines.
- @anthropic-ai/sandbox-runtime — The underlying sandbox engine
- OpenCode Plugins Docs — How to create and use plugins
- Claude Code Sandboxing — Anthropic's sandboxing documentation