OpenCode is not the sort of codebase where you expect a single malformed HTTP request to hand an attacker your shell. But the advisory published by Datadog Security Labs on September 28 paints exactly that picture. Tracked as GHSA-632h-h47v-g4x4, the flaw lets a malicious website execute commands on a developer's machine with almost no user interaction required. No CVE was assigned; defenders have to follow the GitHub advisory identifier instead.
The vulnerability lives where OpenCode's upgrade machinery meets its local web interface. It is a distinct failure mode, combining content-type confusion in the /global/upgrade API with unsafe handling of an attacker-controlled upgrade target. Anomaly, the team behind OpenCode, shipped the fix in version 1.18.22 as defense in depth after Datadog privately reported the issue on August 11.
The Bug: Content-Type Confusion in the Upgrade Path
OpenCode's browser interface, started with opencode serve or opencode web, listens on 127.0.0.1:4096 with no authentication by default. The trouble begins when that local API is told to upgrade the tool.
The /global/upgrade endpoint takes a target value and passes it into a package-manager command intended to install opencode-ai@VERSION. Under the hood this is npm or a compatible resolver. The subtlety is that npm package specifications do not only accept version strings; they also accept URLs pointing at remote tarballs.
That opens the door. An attacker who controls the target can substitute a hosted archive containing a malicious package.json with a preinstall lifecycle script. When the package manager dutifully installs it, the lifecycle script executes with the same privileges as the OpenCode process. A single endpoint, reachable on localhost, becomes a remote code execution primitive.
The affected set is specific and large at once. Datadog found that versions 1.14.30 through 1.18.21 were vulnerable when installed through npm, pnpm, or Bun:
- All OpenCode releases from 1.14.30 to 1.18.21 inclusive, installed via npm, pnpm, or Bun
- Only builds where the browser service is running (opencode serve or opencode web)
- Systems with no password set, or web UI credentials cached in the browser
Why a Website Can Reach 127.0.0.1:4096
The obvious question is why a remote website can talk to a service that binds only to the loopback interface. This is where OpenCode's raw request handler is the weak point.
A conventional JavaScript fetch() to the local API using application/json would trigger a CORS preflight and be blocked. The exploit sidesteps that by submitting an HTML form as a top-level navigation instead. Form submissions as navigations are not stopped by CORS, and they also bypass Local Network Access protections that would normally shield the loopback address from arbitrary origins.
The critical detail is how OpenCode parses that request. Its raw request handler attempted to parse every request body as JSON without first confirming that the declared content type was actually application/json. By using enctype="text/plain" and carefully shaping a hidden field's name and value, an attacker can make the browser produce a body that is valid JSON by accident. That body then points the upgrade endpoint at an attacker-controlled tarball, and installation executes the preinstall script.
Exploitation requires only that a developer with an affected setup visits a crafted page while the OpenCode server runs; the navigation happens with no click on a local link. The full chain is: hostile page, hidden form field, JSON-shaped body, upgrade to a hostile package, lifecycle script, arbitrary command execution.
Scale makes this worth taking seriously. Datadog cites public npm figures showing that 82 vulnerable releases recorded more than 647,000 downloads between September 17 and 23, representing 38.9% of all OpenCode downloads during that window. The figures do not count unique installations or how many users enabled the web service, but a third of a popular agent's installs is not a footnote.
The Fix: Defense in Depth in OpenCode 1.18.22
Anomaly's August 24 patch attacks the problem from two directions. First, it validates the upgrade target as a semantic version, which prevents arbitrary package URLs from being supplied at all. Second, it replaces the raw handler with a content-aware handler that rejects text/plain submissions. OpenCode 1.18.22 returns HTTP 415 Unsupported Media Type for the demonstrated request, terminating the attack path at the API boundary.
The chronology matters for anyone tracing exposure. The vulnerable code path arrived on April 29, with version 1.14.30 released the next day. Datadog discovered and privately reported the flaw on August 11. The fix landed on August 24. The advisory reached public disclosure only on September 28, leaving affected developers about a month of exposure before the details went public.
For developers running OpenCode today, the short checklist is:
- Upgrade to OpenCode 1.18.22 or later and restart any running OpenCode processes
- Verify your installed version and the method you used to install it (npm, pnpm, or Bun)
- Set OPENCODE_SERVER_PASSWORD whenever you use the web interface, and do not expose the service beyond localhost
- Treat unexpected package-manager activity, lifecycle-script execution, or outbound downloads as possible signs of compromise
One caveat: password protection reduces exposure but does not replace patching. Valid basic-authentication credentials cached in the browser can still ride along with a malicious request, so a password is a mitigation, not a cure.
The broader lesson keeps echoing across local-first coding agents. When a tool binds a known port, grants permissive CORS, and runs with no authentication, the line between a trust boundary and a remote code execution primitive is dangerously thin. OpenCode, which launched in June 2025 and now claims more than 208,000 GitHub stars and 16 million monthly developers, has the sort of adoption that turns every flaw into a supply-chain question. Patching is the immediate fix; rethinking what a local API should be willing to trust is the one that lasts.
Comments