Back Securitylabs.Datadoghq Discovering and exploiting a remote code execution vulnerability in OpenCode (GHSA
OpenCode is an open-source AI coding agent that launched in June 2025. It has since grown to more than 200,000 GitHub stars and 16 million monthly users, according to its website. Anomaly develops OpenCode.
In this post, we demonstrate GHSA-632h-h47v-g4x4 , a remote code execution (RCE) vulnerability we discovered in OpenCode. A content-type confusion in the /global/upgrade API endpoint made an underlying code injection flaw exploitable. OpenCode 1.18.22 fixes the vulnerability.
To determine whether you're affected, see How to know if you're affected .
Note: Anomaly chose not to request a CVE for this vulnerability. Anomaly believes that assigning CVEs to vulnerabilities reported through GitHub Security Advisories incentivizes researchers to submit a high volume of low-quality reports.
OpenCode and its web interface
OpenCode is an open-source AI coding agent similar to Claude Code, Codex, and Pi. It lets developers use models from providers such as Anthropic and OpenRouter, as well as locally hosted models.
OpenCode also includes a built-in web interface for running coding sessions from a browser.
You can run opencode serve to start the interface. The equivalent opencode web command also opens it in the default browser.
The web interface requires no authentication by default, but developers can enable basic authentication, which is especially important when exposing OpenCode to a network:
Browsers cache basic authentication credentials, so OpenCode does not prompt for credentials on every request. The exact behavior depends on the browser and version, but modern browsers cache the credentials while the main browser process is running.
Exploiting GHSA-632h-h47v-g4x4 for remote code execution
An attacker can exploit the vulnerability with a malicious npm package tarball and webpage.
First, the attacker hosts a malicious npm package at a public URL. The package can be minimal and contain only a package.json file that executes malicious code through a preinstall script:
The attacker then creates a tarball of the package:
Finally, the attacker hosts a webpage that sends a top-level cross-origin request to :
When a user running a vulnerable OpenCode installation visits the webpage, the preinstall script executes on the machine running OpenCode.
The following video shows the exploit from the perspective of a user running the vulnerable OpenCode 1.18.21:
Identifying and exploiting the vulnerability
The OpenCode API started by opencode serve exposes an upgrade endpoint. This endpoint upgrades OpenCode to either the latest release or a specified version:
The following TypeScript function implements the upgrade endpoint:
When the user installed OpenCode through npm, pnpm, or Bun, the server runs the following command with child_process.spawn() :
The npm package specification accepts both semantic versions, such as 1.18.1, and remote tarballs as install targets. An attacker can therefore supply an arbitrary URL. npm fetches and installs the package tarball from the attacker-controlled server.
To achieve remote code execution, the attacker creates a package with a preinstall lifecycle script:
Exploiting the vulnerability cross-origin
Direct HTTP exploitation requires network access to the OpenCode server, which usually listens on 127.0.0.1 . A malicious webpage, however, may be able to use the victim's browser to reach the local server.
Understanding browser protections for cross-origin requests
An attacker might first try to send the cross-origin request with JavaScript:
Browsers restrict cross-origin fetch() requests through Cross-Origin Resource Sharing (CORS) . Because this request uses the application/json content type, the browser first sends a preflight request to ask whether the endpoint permits the requesting origin, method, and headers:
The browser proceeds only if the server returns matching Access-Control-Allow-Origin , Access-Control-Allow-Methods , and Access-Control-Allow-Headers headers. The OpenCode API returns no Access-Control-Allow-Origin header for , so the browser does not send the POST request:
Modern browsers provide another layer of protection even when an API permits cross-origin requests. Local Network Access , released in Chrome 142, Firefox 151, and Edge 143, prompts users before allowing cross-origin requests to localhost or a hostname that resolves to it.
CORS and Local Network Access do not currently block top-level navigations, by design. An attacker can leverage this to reach the local OpenCode server from a malicious webpage.
An HTML form can submit a POST request through a top-level . To understand how to construct the form, first consider how OpenCode parses HTTP request bodies. The src/server/routes/instance/httpapi/handlers/global.ts controller contains this logic.
The GlobalUpgradeInput definition in src/server/routes/instance/httpapi/groups/global.ts contains a single target field:
The upgradeRaw handler expects JSON, so it rejects a standard HTML form submission with the default application/x-www-form-urlencoded content type:
The form produces the following request and error response:
HTML forms do not support application/json as an enctype . The remaining options are multipart/form-data , which does not produce valid JSON, and text/plain .
The upgradeRaw handler parses the body as JSON without verifying that the Content-Type is application/json . It therefore accepts a text/plain form submission if the body contains valid JSON.
When the browser submits an HTML form with enctype="text/plain" , the HTTP request body looks like:
The browser does not escape or URL-encode the parameter names and values. We can therefore construct a parameter name and value that produce valid JSON:
Parameter name: {"target":"
The browser inserts = between the name and value, producing the following valid JSON body:
The resulting form looks like this:
When a victim visits the webpage, the browser submits the cross-origin POST request as a top-level . The vulnerable OpenCode endpoint accepts the request:
How OpenCode fixed the vulnerability
PR #44686 fixed the vulnerability on August 24, 2026, in commit c6e76e9 . The patch addresses both flaws: it restricts the upgrade target to a semantic version and enforces the request's content type.
First, GlobalUpgradeInput now accepts only a target value that contains a valid semantic version:
Second, the patch replaces Effect's handleRaw function with handle . The handle function inspects the Content-Type header and decodes the request body accordingly, preventing the server-side content-type confusion:
OpenCode 1.18.22 rejects the malicious cross-origin request with an unsupported media type response:
Assessing the community impact
As detailed in How to know if you're affected , the vulnerability affects OpenCode versions 1.14.30 through 1.18.21 when installed through npm, pnpm, or Bun.
According to public npm data , the 82 vulnerable OpenCode versions received more than 647,000 downloads from September 17 through September 23, 2026. This count does not reveal how many unique users or machines downloaded the versions, or how many users run opencode serve or opencode web .
How to know if you're affected
The vulnerability is exploitable only if all of the following conditions apply:
You use OpenCode 1.14.30 through 1.18.21, inclusive.
You run opencode serve or opencode web without password authentication, or your browser has cached credentials from a recent authentication.
You installed OpenCode through npm, pnpm, or Bun. Confirm the installation method by running ls -l "$(command -v opencode)" .
We would like to thank the team at Anomaly for the continuous open communication and for quickly patching the vulnerability once the report was brought to their attention.
Did you find this article helpful?
Related Content research Hidden in Plain Sight: Abusing Entra ID Administrative Units for Sticky Persistence research Entra Agent ID: The blueprint blast radius research Entra Agent ID: Inside a cross-tenant agent compromise research Entra Agent ID: Protect, detect, respond
Hidden in Plain Sight: Abusing Entra ID Administrative Units for Sticky Persistence
Entra Agent ID: The blueprint blast radius
Entra Agent ID: Inside a cross-tenant agent compromise
Entra Agent ID: Protect, detect, respond
We're always looking for talented people to collaborate with
Staff Application Security Engineer Security - Engineering
Staff Application Security Engineer
Security - Engineering
Senior Platform Security Engineer Security - Engineering
Senior Platform Security Engineer
Security - Engineering
Engineering Manager I, Threat Detection Security - Engineering
Engineering Manager I, Threat Detection
Security - Engineering
Security Engineer 2 - Cyber Threat Intelligence Security - Engineering
Security Engineer 2 - Cyber Threat Intelligence
Security - Engineering
Senior Software Engineer - Incident Insights & Readiness Security - Engineering
Senior Software Engineer - Incident Insights & Readiness
Security - Engineering
Engineering Manager I, Commercial Audit Security - Engineering
Engineering Manager I, Commercial Audit
Security - Engineering
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
