opencode-sandboxOpenCode plugin that sandboxes agent commands using @anthropic-ai/sandbox-runtime (seatbelt on macOS, bubblewrap on Linux)
48
+3 in 30 days
1,355
557 in 7 days
56.5
Multi-signal model
2 days ago
2026-08-17
Install and configure
opencode.jsonWrites to this project's opencode.json — applies to this repository only.
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-sandbox@0.5.1"]
}Writes to ~/.config/opencode/opencode.json — applies to every project.
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-sandbox@0.5.1"]
}If you want to modify the plugin locally, install it into the project and reference the local path.
shell
pnpm add -D opencode-sandboxopencode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.
opencode-sandbox
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 currently supported by OpenCode's command-string hook (commands pass through) |
Install
// opencode.json
{
"plugin": ["opencode-sandbox"]
}
The plugin is automatically installed from npm when OpenCode starts.
Linux prerequisites
1. Install bubblewrap:
# Debian/Ubuntu
sudo apt install bubblewrap
# Fedora
sudo dnf install bubblewrap
# Arch
sudo pacman -S bubblewrap
2. 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-restrict
You 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.
What it does
When the agent runs a bash command, the sandbox enforces three layers of protection:
Filesystem write 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
Sensitive file read protection
Access to credential directories is blocked:
$ cat ~/.ssh/id_rsa
cat: /home/user/.ssh/id_rsa: Permission denied
Network allowlist
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)
Default restrictions
Filesystem (deny-read):
~/.ssh,~/.gnupg~/.aws/credentials,~/.config/gcloud~/.npmrc,~/.env
Filesystem (allow-read):
- Empty by default
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.
Configuration
Config files are stored outside the project directory (in ~/.config/opencode-sandbox/) so that sandboxed commands cannot modify them. This prevents indirect prompt injection from weakening the sandbox by overwriting the config.
Config file locations
The plugin searches for configuration in this order (first match wins):
- Environment variable
OPENCODE_SANDBOX_CONFIG(JSON string) - Per-project config
~/.config/opencode-sandbox/projects/<project-name>.json - Global config
~/.config/opencode-sandbox/config.json - Built-in defaults
The <project-name> is the basename of the project directory (e.g., my-app for /home/user/projects/my-app).
If XDG_CONFIG_HOME is set, it is used instead of ~/.config.
Example: Global config
// ~/.config/opencode-sandbox/config.json
{
"filesystem": {
"denyRead": ["~/.ssh", "~/.aws/credentials"],
"allowRead": ["~/.ssh/id_ed25519.pub"],
"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"]
}
}
Path precedence
Path precedence is inherited from @anthropic-ai/sandbox-runtime:
- Read:
allowReadtakes precedence overdenyRead - Write:
denyWritetakes precedence overallowWrite
Example: allow git commit signing with SSH public key
If your Git workflow needs to read a public key (for example ~/.ssh/id_ed25519.pub) while keeping ~/.ssh blocked by default, re-allow only that file:
// ~/.config/opencode-sandbox/config.json
{
"filesystem": {
"denyRead": [
"~/.ssh",
"~/.gnupg",
"~/.aws/credentials",
"~/.azure",
"~/.config/gcloud",
"~/.config/gh",
"~/.kube",
"~/.docker/config.json",
"~/.npmrc",
"~/.netrc",
"~/.env"
],
"allowRead": ["~/.ssh/id_ed25519.pub"]
}
}
Example: Per-project config
// ~/.config/opencode-sandbox/projects/my-app.json
{
"network": {
"allowedDomains": ["my-internal-api.company.com"]
}
}
Environment variable
OPENCODE_SANDBOX_CONFIG='{"filesystem":{"denyRead":["~/.ssh"]},"network":{"allowedDomains":["github.com"]}}' opencode
Example allowing only the SSH public key to be read:
OPENCODE_SANDBOX_CONFIG='{"filesystem":{"denyRead":["~/.ssh","~/.gnupg","~/.aws/credentials","~/.azure","~/.config/gcloud","~/.config/gh","~/.kube","~/.docker/config.json","~/.npmrc","~/.netrc","~/.env"],"allowRead":["~/.ssh/id_ed25519.pub"]}}' opencode
Disable
OPENCODE_DISABLE_SANDBOX=1 opencode
Or in any config file:
{
"disabled": true
}
How it works
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.
Sandbox initialization is deferred until the first bash command, so the plugin does not interfere with OpenCode startup. Plugin diagnostics are sent through OpenCode's structured logger instead of being printed into the TUI. Sandbox violations are correlated with each individual tool call, including concurrent or repeated commands.
Windows status
@anthropic-ai/sandbox-runtime supports Windows through an argv-and-environment API, while OpenCode currently exposes this plugin's bash hook as a command string. Until those interfaces can be connected safely, this plugin leaves Windows commands unsandboxed rather than claiming protection it cannot enforce.
Fail-open design
If anything goes wrong (sandbox init fails, wrapping fails, platform unsupported), commands run normally without sandbox. The plugin never breaks your workflow.
Contributing
See CONTRIBUTING.md for development setup, architecture, and guidelines.
Related
- @anthropic-ai/sandbox-runtime — The underlying sandbox engine
- OpenCode Plugins Docs — How to create and use plugins
- Claude Code Sandboxing — Anthropic's sandboxing documentation