Skip to content
GHSA-632h-h47v-g4x4

GHSA-632h-h47v-g4x4

github.com • September 24, 2026

This issue affects users who are running the OpenCode HTTP server through opencode serve and whose OpenCode installation is managed by npm, pnpm, or Bun. The standalone CLI upgrade command is not itself a cross-origin attack surface.

In the affected configuration, a webpage can submit a cross-site request to /global/upgrade . The endpoint accepts a general package specifier as target , allowing the npm-compatible package manager to install an attacker-controlled package and execute its lifecycle scripts.

Users who are not running opencode serve are not exposed to this cross-site request path. Installations managed through curl, Homebrew, Chocolatey, or Scoop do not interpret target as an alternate npm package and are not affected by this arbitrary-package installation path.

Password protection prevents unauthenticated requests unless the browser has cached valid HTTP Basic credentials; with cached credentials, a cross-site top-level form can include them.

opencode serve exposes a /global/upgrade endpoint that allows opencode to dynamically update itself, specifying the target version:

When opencode is installed through npm, the backend then calls npm install -g [email protected] . However, npm package specs can also point to remote tarballs. Therefore an attacker can supply an arbitrary package URL:

In addition, the /global/upgrade endpoint doesn't check that the request is coming from a trusted Origin . These two things together allow any webpage to trigger arbitrary remote code execution without further user interaction.

The text/plain content type is important because browsers cannot submit cross-origin HTML forms as application/json , but opencode parses the submitted text/plain form body as JSON anyway.

Attacker setup : On an attacker-controlled server, create a clone of the opencode npm package and add malicious code to it, for instance in a preinstall script:

Then create the following malicious webpage:

(Note: The unusual hidden input formatting is intentional. With `enctype="text/plain"`, the browser will serialize form fields as `name=value`; by splitting the JSON across `name` and `value`, the submitted body becomes valid JSON: {"target":" .)

Finally, serve the malicious webpage and tarball:

Attack : Assuming the victim is running opencode serve in the background, they only have to visit the attacker's IP and load the HTML. This causes the browser to perform the following HTTP request:

This causes opencode to run npm install -g opencode-ai@ , which fetches the package tarball from the attacker-controlled server and installs it on the machine.

Notes on exploitation:

This exploitation method works when opencode was installed through npm, pnpm or bun.

This works including if a password is set through OPENCODE_SERVER_PASSWORD as long as the user has authenticated once. In this case, the browser sends cached basic authentication credentials.

This works including on modern browsers (latest Chrome), because these protections don't apply to top-level .

A malicious webpage can execute arbitrary code as the user running OpenCode when all of the following conditions hold:

The victim is running the OpenCode HTTP server through opencode serve .

OpenCode was installed through npm, pnpm, or Bun.

The browser can reach the server and, when server authentication is enabled, has cached valid HTTP Basic credentials.

The victim visits an attacker-controlled webpage.

The arbitrary-package installation impact does not apply to curl, Homebrew, Chocolatey, or Scoop installations.

opencode/packages/opencode/src/installation/index.ts

Lines 265 to 278 in d041eee

I believe impacted versions are v1.14.30 through v1.18.16, because v1.14.30 ( 6015084 ) introduces a change that introduced a raw handler that reads request.text and parses it as JSON, making text/plain requests accepted.

The fix is implemented in PR #44686 :

/global/upgrade now uses the standard typed HTTP API request decoder.

Requests must use the supported JSON content type.

The request must include an explicit target .

target must be a valid semantic version rather than a general package specifier.

Bodyless upgrade requests are rejected.

These changes prevent alternate package sources from reaching the package-manager upgrade command and ensure browser requests use the endpoint's declared JSON contract. The patched release version is 1.18.22

Extracted Entities