Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
80 changes: 70 additions & 10 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,85 @@ submit one that will be an automatic ban from the project.

OpenCode is an AI-powered coding assistant that runs locally on your machine. It provides an agent system with access to powerful tools including shell execution, file operations, and web access.

### No Sandbox
### Sandboxing (macOS only, experimental)

OpenCode does **not** sandbox the agent. The permission system exists as a UX feature to help users stay aware of what actions the agent is taking - it prompts for confirmation before executing commands, writing files, etc. However, it is not designed to provide security isolation.
OpenCode can optionally sandbox certain command execution paths on macOS using `sandbox-exec`. This feature is **opt-in**, **experimental**, and **off by default**. It is not available on Linux or Windows.

If you need true isolation, run OpenCode inside a Docker container or VM.
#### Covered surfaces

| Surface | Sandbox profile | Excluded-command check | Unsandboxed retry |
| ----------------------------------------------------------- | --------------- | ---------------------- | ----------------------------------- |
| **Bash tool** (agent-issued non-interactive commands) | Yes | Yes (pre-spawn) | Yes (`bash:unsandboxed` permission) |
| **Session command path** (user-initiated command execution) | Yes | Yes (pre-spawn) | Yes (`bash:unsandboxed` permission) |
| **PTY interactive sessions** | Yes | Initial spawn only | No |

PTY sessions apply the sandbox profile to the initial process spawn and check `excluded_commands` before spawning. In-band command filtering inside a running PTY session is **not** performed — once a PTY shell is running, commands typed into it are not individually inspected or blocked.

#### Presets

Built-in presets control mode, network, and permission defaults:

| Preset | Mode | Network | Notes |
| ------------- | ----------------- | ------- | ----------------------------------------- |
| **`default`** | `workspace-write` | No | Read system paths, read/write workspace |
| **`strict`** | `read-only` | No | Writes limited to `/tmp`; bash/edit = ask |
| **`network`** | `workspace-write` | Yes | Same as default but allows network access |

Custom presets can be defined under `experimental.sandbox.presets` in `opencode.json`. Selecting a preset via the `preset` field resolves the named preset, then any sibling sandbox fields (`mode`, `network`, `protected_roots`, `extra_read_roots`, `extra_write_roots`) override the preset values.

#### Protected roots

Inside writable workspace roots, `.git` and `.opencode` are always write-protected. If the workspace is a git worktree, the resolved gitdir target (read from the `.git` file) is also write-protected. These deny rules are emitted after the write-allow rules in the `sandbox-exec` profile, so they take precedence.

#### Modes

- **`workspace-write`** (default) — the sandboxed process can read system paths and read/write within the project workspace.
- **`read-only`** — the sandboxed process can read, and writes are limited to `/tmp`, `/private/tmp`, and explicitly configured extra write roots.

There is no `danger-full-access` or unrestricted mode. Even the most permissive built-in preset (`network`) still enforces filesystem boundaries and protected roots.

#### Configuration options

All options live under `experimental.sandbox` in `opencode.json`:

- **`preset`** — selects a built-in or custom preset by name. Defaults to `default`.
- **`presets`** — defines custom presets keyed by name. Each preset can specify `mode`, `network`, `protected_roots`, `permission`, `extra_read_roots`, and `extra_write_roots`.
- **`mode`** — overrides the preset mode (`workspace-write` or `read-only`).
- **`network`** — overrides the preset network policy (`true` or `false`).
- **`protected_roots`** — overrides the preset list of write-protected workspace-relative paths (defaults to `.git` and `.opencode`).
- **`extra_read_roots`** — additional absolute paths the sandbox allows reading.
- **`extra_write_roots`** — additional absolute paths the sandbox allows writing.
- **`excluded_commands`** — a pre-spawn deny list of command prefixes. Matched commands are blocked before execution on all covered surfaces.
- **`fail_if_unavailable`** — when `true`, hard-fails activation if sandboxing is enabled but `sandbox-exec` is missing or the platform is unsupported.
- **`extra_deny_paths`** — extends the default set of denied paths (secrets directories like `.ssh`, `.gnupg`, `.aws`, etc.).
- **`allow_unsandboxed_retry`** — when `true`, adds a distinct `bash:unsandboxed` permission-gated retry for the bash tool and session command path only. If a sandboxed command fails due to a sandbox denial, the user is prompted to allow an unsandboxed re-execution. PTY sessions do **not** support unsandboxed retry.

#### Not covered

The following are explicitly **not** sandboxed:

- MCP server processes (local stdio servers and SSE connections)
- Internal spawn utilities (`util/process.ts`, `cross-spawn-spawner.ts`) not routed through the three surfaces above
- Domain/proxy-mediated network controls
- All non-macOS platforms (Linux, Windows, etc.)

The permission system (confirmation prompts before commands, file writes, etc.) remains a UX layer, not a security boundary. A sandbox denial can still block a command that the permission system allowed.

For stronger isolation, run OpenCode inside a Docker container or VM.

### Server Mode

Server mode is opt-in only. When enabled, set `OPENCODE_SERVER_PASSWORD` to require HTTP Basic Auth. Without this, the server runs unauthenticated (with a warning). It is the end user's responsibility to secure the server - any functionality it provides is not a vulnerability.

### Out of Scope

| Category | Rationale |
| ------------------------------- | ----------------------------------------------------------------------- |
| **Server access when opted-in** | If you enable server mode, API access is expected behavior |
| **Sandbox escapes** | The permission system is not a sandbox (see above) |
| **LLM provider data handling** | Data sent to your configured LLM provider is governed by their policies |
| **MCP server behavior** | External MCP servers you configure are outside our trust boundary |
| **Malicious config files** | Users control their own config; modifying it is not an attack vector |
| Category | Rationale |
| ------------------------------------- | ---------------------------------------------------------------------------- |
| **Server access when opted-in** | If you enable server mode, API access is expected behavior |
| **Sandbox escapes (uncovered paths)** | MCP servers, non-macOS execution, and in-band PTY commands are not sandboxed |
| **LLM provider data handling** | Data sent to your configured LLM provider is governed by their policies |
| **MCP server behavior** | External MCP servers you configure are outside our trust boundary |
| **Malicious config files** | Users control their own config; modifying it is not an attack vector |

---

Expand Down
2 changes: 0 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions packages/app/src/i18n/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,13 @@ export const dict = {
"settings.permissions.tool.list.description": "سرد الملفات داخل دليل",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "تشغيل أوامر shell",
"settings.permissions.tool.bash_unsandboxed.title": "Bash (بدون صندوق حماية)",
"settings.permissions.tool.bash_unsandboxed.description": "أعد محاولة تشغيل أمر shell بدون قيود صندوق الحماية",
"settings.permissions.tool.bash_unsandboxed_network.title": "Bash (بدون صندوق حماية)",
"settings.permissions.tool.bash_unsandboxed_network.description":
"تم تعطيل الشبكات في صندوق الحماية، لذلك ربما فشلت المحاولة السابقة بسبب صندوق الحماية.",
"settings.permissions.tool.bash_unsandboxed_explicit.title": "Bash (بدون صندوق حماية)",
"settings.permissions.tool.bash_unsandboxed_explicit.description": "طلب الأمر التشغيل بدون قيود صندوق الحماية.",
"settings.permissions.tool.task.title": "مهمة",
"settings.permissions.tool.task.description": "تشغيل الوكلاء الفرعيين",
"settings.permissions.tool.skill.title": "مهارة",
Expand Down
9 changes: 9 additions & 0 deletions packages/app/src/i18n/br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,15 @@ export const dict = {
"settings.permissions.tool.list.description": "Listar arquivos dentro de um diretório",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Executar comandos shell",
"settings.permissions.tool.bash_unsandboxed.title": "Bash (sem sandbox)",
"settings.permissions.tool.bash_unsandboxed.description":
"Tente novamente um comando de shell sem restrições de sandbox",
"settings.permissions.tool.bash_unsandboxed_network.title": "Bash (sem sandbox)",
"settings.permissions.tool.bash_unsandboxed_network.description":
"A rede do sandbox está desativada, então a tentativa anterior pode ter falhado por causa do sandbox.",
"settings.permissions.tool.bash_unsandboxed_explicit.title": "Bash (sem sandbox)",
"settings.permissions.tool.bash_unsandboxed_explicit.description":
"O comando solicitou a execução sem restrições de sandbox.",
"settings.permissions.tool.task.title": "Tarefa",
"settings.permissions.tool.task.description": "Lançar sub-agentes",
"settings.permissions.tool.skill.title": "Habilidade",
Expand Down
9 changes: 9 additions & 0 deletions packages/app/src/i18n/bs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,15 @@ export const dict = {
"settings.permissions.tool.list.description": "Listanje datoteka unutar direktorija",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Pokretanje shell komandi",
"settings.permissions.tool.bash_unsandboxed.title": "Bash (bez sandboxa)",
"settings.permissions.tool.bash_unsandboxed.description":
"Ponovo pokušaj pokrenuti shell naredbu bez ograničenja sandboxa",
"settings.permissions.tool.bash_unsandboxed_network.title": "Bash (bez sandboxa)",
"settings.permissions.tool.bash_unsandboxed_network.description":
"Mreža u sandboxu je onemogućena, pa je prethodni pokušaj možda neuspješno završio zbog sandboxa.",
"settings.permissions.tool.bash_unsandboxed_explicit.title": "Bash (bez sandboxa)",
"settings.permissions.tool.bash_unsandboxed_explicit.description":
"Naredba je zatražila pokretanje bez ograničenja sandboxa.",
"settings.permissions.tool.task.title": "Zadatak",
"settings.permissions.tool.task.description": "Pokretanje pod-agenta",
"settings.permissions.tool.skill.title": "Vještina",
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/i18n/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,14 @@ export const dict = {
"settings.permissions.tool.list.description": "List filer i en mappe",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Kør shell-kommandoer",
"settings.permissions.tool.bash_unsandboxed.title": "Bash (uden sandbox)",
"settings.permissions.tool.bash_unsandboxed.description": "Prøv en shell-kommando igen uden sandbox-begrænsninger",
"settings.permissions.tool.bash_unsandboxed_network.title": "Bash (uden sandbox)",
"settings.permissions.tool.bash_unsandboxed_network.description":
"Sandbox-netværk er deaktiveret, så det forrige forsøg kan være mislykket på grund af sandboxen.",
"settings.permissions.tool.bash_unsandboxed_explicit.title": "Bash (uden sandbox)",
"settings.permissions.tool.bash_unsandboxed_explicit.description":
"Kommandoen anmodede om at køre uden sandbox-begrænsninger.",
"settings.permissions.tool.task.title": "Opgave",
"settings.permissions.tool.task.description": "Start underagenter",
"settings.permissions.tool.skill.title": "Færdighed",
Expand Down
9 changes: 9 additions & 0 deletions packages/app/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,15 @@ export const dict = {
"settings.permissions.tool.list.description": "Dateien in einem Verzeichnis auflisten",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Shell-Befehle ausführen",
"settings.permissions.tool.bash_unsandboxed.title": "Bash (ohne Sandbox)",
"settings.permissions.tool.bash_unsandboxed.description":
"Einen Shell-Befehl ohne Sandbox-Einschränkungen erneut ausführen",
"settings.permissions.tool.bash_unsandboxed_network.title": "Bash (ohne Sandbox)",
"settings.permissions.tool.bash_unsandboxed_network.description":
"Das Sandbox-Netzwerk ist deaktiviert, daher könnte der vorherige Versuch an der Sandbox gescheitert sein.",
"settings.permissions.tool.bash_unsandboxed_explicit.title": "Bash (ohne Sandbox)",
"settings.permissions.tool.bash_unsandboxed_explicit.description":
"Der Befehl hat angefordert, ohne Sandbox-Einschränkungen ausgeführt zu werden.",
"settings.permissions.tool.task.title": "Aufgabe",
"settings.permissions.tool.task.description": "Unteragenten starten",
"settings.permissions.tool.skill.title": "Fähigkeit",
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,12 @@ export const dict = {
"settings.permissions.tool.list.description": "List files within a directory",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Run shell commands",
"settings.permissions.tool.bash_unsandboxed.title": "Bash (Unsandboxed)",
"settings.permissions.tool.bash_unsandboxed.description": "Retry a shell command without sandbox restrictions",
"settings.permissions.tool.bash_unsandboxed_network.title": "Bash (Unsandboxed)",
"settings.permissions.tool.bash_unsandboxed_network.description": "Sandbox networking is disabled, so the previous attempt may have failed because of the sandbox.",
"settings.permissions.tool.bash_unsandboxed_explicit.title": "Bash (Unsandboxed)",
"settings.permissions.tool.bash_unsandboxed_explicit.description": "The command requested to run without sandbox restrictions.",
"settings.permissions.tool.task.title": "Task",
"settings.permissions.tool.task.description": "Launch sub-agents",
"settings.permissions.tool.skill.title": "Skill",
Expand Down
9 changes: 9 additions & 0 deletions packages/app/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,15 @@ export const dict = {
"settings.permissions.tool.list.description": "Listar archivos dentro de un directorio",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Ejecutar comandos de shell",
"settings.permissions.tool.bash_unsandboxed.title": "Bash (sin sandbox)",
"settings.permissions.tool.bash_unsandboxed.description":
"Reintentar un comando de shell sin restricciones de sandbox",
"settings.permissions.tool.bash_unsandboxed_network.title": "Bash (sin sandbox)",
"settings.permissions.tool.bash_unsandboxed_network.description":
"La red del sandbox está deshabilitada, por lo que el intento anterior pudo haber fallado a causa del sandbox.",
"settings.permissions.tool.bash_unsandboxed_explicit.title": "Bash (sin sandbox)",
"settings.permissions.tool.bash_unsandboxed_explicit.description":
"El comando solicitó ejecutarse sin restricciones de sandbox.",
"settings.permissions.tool.task.title": "Tarea",
"settings.permissions.tool.task.description": "Lanzar sub-agentes",
"settings.permissions.tool.skill.title": "Habilidad",
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,14 @@ export const dict = {
"settings.permissions.tool.list.description": "Lister les fichiers dans un répertoire",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Exécuter des commandes shell",
"settings.permissions.tool.bash_unsandboxed.title": "Bash (sans sandbox)",
"settings.permissions.tool.bash_unsandboxed.description": "Réessayer une commande shell sans restrictions de sandbox",
"settings.permissions.tool.bash_unsandboxed_network.title": "Bash (sans sandbox)",
"settings.permissions.tool.bash_unsandboxed_network.description":
"Le réseau du sandbox est désactivé, donc la tentative précédente a peut-être échoué à cause du sandbox.",
"settings.permissions.tool.bash_unsandboxed_explicit.title": "Bash (sans sandbox)",
"settings.permissions.tool.bash_unsandboxed_explicit.description":
"La commande a demandé à s'exécuter sans restrictions de sandbox.",
"settings.permissions.tool.task.title": "Tâche",
"settings.permissions.tool.task.description": "Lancer des sous-agents",
"settings.permissions.tool.skill.title": "Compétence",
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,14 @@ export const dict = {
"settings.permissions.tool.list.description": "ディレクトリ内のファイル一覧表示",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "シェルコマンドの実行",
"settings.permissions.tool.bash_unsandboxed.title": "Bash(サンドボックスなし)",
"settings.permissions.tool.bash_unsandboxed.description": "サンドボックスの制限なしでシェルコマンドを再試行します",
"settings.permissions.tool.bash_unsandboxed_network.title": "Bash(サンドボックスなし)",
"settings.permissions.tool.bash_unsandboxed_network.description":
"サンドボックスのネットワークが無効になっているため、前回の試行はサンドボックスが原因で失敗した可能性があります。",
"settings.permissions.tool.bash_unsandboxed_explicit.title": "Bash(サンドボックスなし)",
"settings.permissions.tool.bash_unsandboxed_explicit.description":
"このコマンドはサンドボックスの制限なしで実行するよう要求しました。",
"settings.permissions.tool.task.title": "タスク",
"settings.permissions.tool.task.description": "サブエージェントの起動",
"settings.permissions.tool.skill.title": "スキル",
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,14 @@ export const dict = {
"settings.permissions.tool.list.description": "디렉터리 내 파일 나열",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "셸 명령어 실행",
"settings.permissions.tool.bash_unsandboxed.title": "Bash (샌드박스 없음)",
"settings.permissions.tool.bash_unsandboxed.description": "샌드박스 제한 없이 셸 명령을 다시 시도합니다",
"settings.permissions.tool.bash_unsandboxed_network.title": "Bash (샌드박스 없음)",
"settings.permissions.tool.bash_unsandboxed_network.description":
"샌드박스 네트워킹이 비활성화되어 있으므로 이전 시도는 샌드박스 때문에 실패했을 수 있습니다.",
"settings.permissions.tool.bash_unsandboxed_explicit.title": "Bash (샌드박스 없음)",
"settings.permissions.tool.bash_unsandboxed_explicit.description":
"명령이 샌드박스 제한 없이 실행되도록 요청했습니다.",
"settings.permissions.tool.task.title": "작업",
"settings.permissions.tool.task.description": "하위 에이전트 실행",
"settings.permissions.tool.skill.title": "기술",
Expand Down
Loading
Loading