opencode opencode.ai ↗

Open-source terminal agent from SST. CVE-2026-22812 (unauthenticated HTTP server RCE, fixed 1.0.216) and CVE-2026-22813 (markdown XSS, fixed 1.1.10). Plugin and MCP local servers execute arbitrary code at startup.

1

Patch and Audit the Install

CVE-2026-22812 (unauthenticated RCE via the local HTTP server — any malicious webpage could execute shell commands) is fixed in 1.0.216; CVE-2026-22813 (HTML injection in the markdown renderer, no DOMPurify/CSP) is fixed in 1.1.10. Anything older is exploitable from a drive-by browser tab.

Real incident CVE-2026-22812 turned any visited webpage into a path to local shell execution while opencode serve was running on default loopback — the wildcard --cors allowed cross-origin POSTs to /session/* endpoints. GitHub Advisory
opencode --version # require >= 1.1.10

Tip: track GitHub Security Advisories on sst/opencode; uninstall old global binaries before installing the new one.

2

Keep the HTTP Server Private

opencode serve binds 127.0.0.1:4096 by default and exposes /tui, /session/*, and the full OpenAPI spec at /doc. Never bind to 0.0.0.0 or widen --cors to wildcards — that is the pre-1.0.216 RCE class.

OPENCODE_SERVER_PASSWORD='<long-random>' \ opencode serve --hostname 127.0.0.1 --port 4096

Tip: leave --mdns off, set explicit --cors origins, front any remote exposure with an SSH tunnel or mTLS reverse proxy.

3

Enforce Gateway/Basic Auth on Server Mode

The server is unauthenticated unless OPENCODE_SERVER_PASSWORD is set. Without it, anything local — including a browser page hitting localhost — can drive the agent through /tui or session endpoints.

export OPENCODE_SERVER_USERNAME=ops export OPENCODE_SERVER_PASSWORD="$(openssl rand -base64 32)"

Tip: rotate the password per machine, store it in your OS keychain, refuse to start serve if the env var is empty.

4

Workspace Isolation and External-Directory Guard

opencode auto-loads opencode.json and .opencode/ from whichever directory you launch in — untrusted repos can ship hostile MCP commands, plugins, or agent files. external_directory defaults to "ask"; keep it that way.

{ "permission": { "external_directory": { "*": "ask", "~/projects/trusted/**": "allow" } } }

Tip: run untrusted repos inside a container or VM, disable project-level plugin/MCP loading until you've reviewed opencode.json and .opencode/.

5

Tool Allowlist via permission

opencode's 13 built-in tools (bash, edit, write, webfetch, task, etc.) default to "allow". Tighten with pattern rules — last-match wins, so put * first.

{ "permission": { "bash": { "*": "ask", "git status": "allow", "rm *": "deny", "curl *": "deny" }, "edit": { "*": "ask", "node_modules/**": "deny" }, "webfetch": "ask", "task": "ask" } }

Tip: never invoke headless opencode run -p ... against an untrusted prompt — -p auto-approves every permission. Use "ask" policies in interactive sessions and a deny-by-default policy under CI.

6

Credentials Hygiene — auth.json and .env

Provider keys from opencode auth login land in ~/.local/share/opencode/auth.json (plain JSON, no encryption documented), and OAuth tokens for MCP land in ~/.local/share/opencode/mcp-auth.json. opencode also auto-loads .env from the project root.

{ "permission": { "read": { "*": "allow", "*.env": "deny", "*.env.*": "deny", "*.env.example": "allow" } } }

Tip: chmod 600 ~/.local/share/opencode/auth.json, prefer {env:ANTHROPIC_API_KEY} substitution over baking keys into config, never commit opencode.json containing inline keys.

7

Lock Down MCP Servers

MCP local servers run an arbitrary command array at startup with no prompt and no confirmation — a malicious opencode.json is straight-line code execution.

{ "mcp": { "github": { "type": "remote", "url": "https://mcp.github.com", "headers": { "Authorization": "Bearer {env:GH_MCP_TOKEN}" }, "enabled": true }, "filesystem": { "type": "local", "command": ["npx","-y","@org/[email protected]"], "enabled": false } } }

Tip: review every MCP entry on git pull, keep the count small, remember plugin tool.execute.before hooks do not intercept subagent calls (issue #5894).

8

Constrain Subagents and Custom Agents

Agents are markdown files in .opencode/agents/*.md with YAML frontmatter that can override global permissions. A repo-supplied subagent can quietly re-enable bash or edit.

--- description: Read-only code reviewer mode: subagent permission: edit: deny write: deny bash: deny webfetch: deny task: deny ---

Tip: treat .opencode/agents/ and .opencode/commands/ as code — review in PRs; prefer global agents in ~/.config/opencode/agents/ over project ones for sensitive roles.

9

Plugin Safety

Plugins in .opencode/plugins/ (and the global equivalent) are JS/TS modules auto-loaded at startup, with npm deps cached in ~/.cache/opencode/node_modules/. They have full Node privileges. A project that ships a plugin owns your shell.

ls -la .opencode/plugins/ ~/.config/opencode/plugins/

Tip: disable plugin auto-load for unfamiliar repos (move/rename the directory before first launch), pin plugin versions, audit tool.execute.before hooks — they're bypassed by subagents, so layer them behind permission rules.

10

Prompt-Injection and Output-Rendering Defense

CVE-2026-22813 XSS shows model output is dangerous: pasted web content, MCP tool responses, and remote files can carry injected instructions or HTML. Use webfetch/websearch sparingly with "ask", set doom_loop: "ask" so repeated identical tool calls pause.

{ "permission": { "webfetch": "ask", "websearch": "ask", "doom_loop": "ask" } }

Tip: never paste raw issue/email/web text into a --prompt invocation with auto-approve; route untrusted inputs through a read-only subagent whose tool surface is denied by default.

References & further reading