Neutron, our AI engine, scored 97.1% on UC Berkeley's CyberGym benchmark. Learn more

Deep Dive

Deep Dive

OpenCode MCP OAuth Sign-In RCE on Windows, Fixed in 1.18.33

In OpenCode 1.0.137 to 1.18.32 on Windows and WSL, a malicious MCP server's OAuth sign-in URL reaches PowerShell and runs code as the developer; fixed in 1.18.33, with no CVE.

TL;DR

  • During an MCP OAuth sign-in, the server being authenticated chooses the web address the developer's browser is sent to. In OpenCode on Windows and WSL, versions 1.0.137 through 1.18.32 passed that server-controlled URL to the open package, which built a PowerShell command around it without escaping it, so an address carrying a PowerShell subexpression executed as the developer.
  • Pillar Security, which found the flaw and reported it privately on August 29, 2026, rates it Critical with a proposed CVSS v3.1 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), CWE-78. That is Pillar's proposed rating: the documented attack still requires the developer to run opencode mcp auth, after which the handshake needs no further prompt or consent. There is no CVE and no vendor advisory: Anomaly, OpenCode's developer, holds that a malicious remote MCP server is outside its threat model, so scanners that match versions against published advisories have no record for this flaw.
  • macOS and Linux are not affected, because OpenCode opens the browser there without passing the URL through a shell. No attacks through the flaw have been reported, and Pillar published no indicators of compromise.
  • Upgrade to OpenCode 1.18.33 or later, released September 28, 2026. If you cannot upgrade yet, remove every remote MCP server you have not fully vetted from the OpenCode configuration on Windows and WSL machines.

Am I affected?

You are affected if you run opencode-ai 1.0.137–1.18.32 or @opencode/cli 2.0.0–2.0.17 on Windows or WSL with a remote MCP server whose responses you do not control, and you start that server's sign-in. All of these must hold:

  • You run OpenCode on Windows or the Windows Subsystem for Linux (WSL). macOS and Linux installs are not affected: the browser launcher on those platforms never passes the URL through a shell.
  • The version is opencode-ai 1.0.137 to 1.18.32 inclusive, or @opencode/cli 2.0.0 to 2.0.17 inclusive.
  • At least one remote MCP server is configured whose responses you do not fully control. The server did not need to be malicious when it was configured: the payload arrives in an HTTP response at sign-in time, so a server compromised later, a hijacked registry entry, a typosquat or an expired domain that changed hands can carry the same address.
  • The developer starts that server's sign-in, by running opencode mcp auth. Having the server configured, or opening a session, does not reach the browser step.
Package Affected versions Fixed version
opencode-ai on npm, Windows and WSL 1.0.137 to 1.18.32 1.18.33 (September 28, 2026)
opencode-ai on npm, macOS and Linux not affected not applicable
@opencode/cli on npm, Windows and WSL 2.0.0 to 2.0.17 2.0.18 (September 25, 2026)

opencode-ai recorded 2,180,172 npm downloads during October 16–22, 2026. Downloads are a reach indicator, not a count of affected installations.

Check what you run and which servers are configured:

opencode mcp list                               # which MCP servers are configured
npm ls -g opencode-ai @opencode/cli 2>/dev/null  # npm installs and their versions
brew list --versions opencode 2>/dev/null       # Homebrew installs

The fix is in the project's shared source tree since September 25, 2026, so upgrading either package to its current release clears the flaw; Pillar Security's disclosure matrix covers the opencode-ai line.

How do I fix it?

Upgrade opencode-ai to 1.18.33 or later, or @opencode/cli to 2.0.18 or later:

npm i -g opencode-ai@latest

For the @opencode/cli package line instead:

npm i -g @opencode/cli@latest

If you cannot upgrade a Windows or WSL machine yet:

  • Remove every remote MCP server you have not vetted from that machine's OpenCode configuration (opencode mcp list shows what remains).
  • Treat a server you cannot remove as an accepted exposure: its sign-in page is where the payload would arrive.
  • Local MCP servers that run commands you install yourself are outside this path, which needs a remote server's OAuth handshake.

What is the root cause?

The OAuth discovery handshake let the remote MCP server choose the sign-in URL, and OpenCode on Windows handed that untrusted URL to a browser-opening helper that interpolated it into a PowerShell command without escaping it.

The chain, in OpenCode 1.18.32: the server's discovery document names an authorization_endpoint, a free-form string the server picks, which flows through the MCP SDK into oauth-provider.ts, then mcp/index.ts, then browser.ts, and finally into open(). OpenCode added the call that reaches open() with the server-supplied URL in commit 509e43d6 on December 7, 2025, pinning open at version 10.1.2.

On macOS and Linux, open runs open or xdg-open through spawn with an argument vector, so no shell is involved. On Windows, open 10.1.2 builds a PowerShell command, and the escaping is asymmetric, as its own source shows:

// open@10.1.2, the Windows path (lines 242 to 256)
242:  encodedArguments.push(`"\`${app}\`""`);        // app gets escaped
247:  encodedArguments.push(`"${options.target}"`);    // target does not
256:  options.target = Buffer.from(
         encodedArguments.join(' '), 'utf16le').toString('base64');

Line 242 escapes the application name. Line 247, the target, which is exactly where the remote URL lands, is not escaped at all. The joined arguments are base64-encoded and handed to powershell -EncodedCommand.

PowerShell expands $(...) subexpressions inside double-quoted strings. That is the bug: a remote string, chosen by whoever controls the MCP server's response, becomes part of a PowerShell command that the developer's machine executes at sign-in.

How is it exploited?

No attacks have been reported, and the proof of concept is public in Pillar Security's disclosure. Their payload is a perfectly ordinary HTTPS address:

https://evil.test/$([IO.File]::WriteAllText('C:\Users\Public\pwned.txt','RCE'))

The attack, step by step:

  1. The attacker controls a remote MCP server the developer has configured, or one they can influence at sign-in time: a takeover, a hijacked registry entry, a typosquat, or an expired domain that changed hands.
  2. The developer starts that server's sign-in, by running opencode mcp auth. No further interaction or consent is needed; the handshake is automatic by design.
  3. The server's discovery document returns the crafted URL as its authorization_endpoint.
  4. OpenCode passes the URL to open 10.1.2, which interpolates it into the PowerShell command without escaping and runs it.
  5. PowerShell evaluates the $(...) subexpression before it resolves the command, so the payload executes even though the browser launch then fails. OpenCode discards that failure.

The payload runs with the developer's own privileges, on the machine that holds the developer's credentials, tokens, source trees and CI access.

How does the attack flow?

The attack is a single server-controlled URL that becomes a PowerShell command at sign-in time.

flowchart LR A["Developer starts sign-in to
a remote MCP server"] --> B["Server's discovery document
returns a crafted URL"] B --> C["OpenCode hands the URL to
open@10.1.2 on Windows or WSL"] C --> D["open builds a PowerShell command;
the target URL is not escaped"] D --> E["PowerShell evaluates the $(...)
subexpression before launching"] E --> F["Payload runs as the developer;
the failed launch is discarded"] style D fill:#f8d7da

The gap is at D: nothing escaped the server-supplied URL before it entered the command line.

What can an attacker do?

Run arbitrary code as the developer on a Windows or WSL machine, which in practice means everything the developer can reach: source trees, credentials and tokens on the disk, git and cloud CLIs, CI access, and any MCP server the agent can talk to. Pillar rates the flaw Critical, proposed CVSS v3.1 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), CWE-78, with CWE-88 secondary.

The conditions the attack needs:

  • opencode-ai 1.0.137 to 1.18.32 or @opencode/cli 2.0.0 to 2.0.17 on Windows or WSL. macOS and Linux never pass the URL through a shell.
  • A remote MCP server whose OAuth discovery response the attacker controls at sign-in time. It did not need to be malicious when configured.
  • The developer starting that server's sign-in. Nothing else, no prompt and no additional consent.
  • Scheme validation does not help: the payload is a plain https:// URL. It passes the MCP TypeScript SDK's javascript:/data:/vbscript: denylist and OpenCode's two new scheme checks. It would also pass the stricter http/https allowlist the SDK considered but never shipped.

Why does the fix work?

OpenCode 1.18.33, merged as commit 29f07e0c through pull request #51414, closes the flaw in three layers, and the order matters:

--- a/packages/core/src/open.ts (new file)
+import open from "open"
+
+export function openUrl(input: string) {
+  const url = URL.canParse(input) ? new URL(input) : undefined
+  if (!url || (url.protocol !== "http:" && url.protocol !== "https:"))
+    return Promise.reject(new Error(`Only http and https links can be opened in the browser: ${input}`))
+  return open(url.href)
+}

--- a/packages/opencode/src/mcp/oauth-provider.ts
   async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
+    if (authorizationUrl.protocol !== "http:" && authorizationUrl.protocol !== "https:")
+      throw new Error(
+        `MCP server "${this.serverUrl}" returned a ${authorizationUrl.protocol} authorization URL; only http and https are supported`,
+      )
     await this.callbacks.onRedirect(authorizationUrl)
   }

--- a/package.json
-    "open": "10.1.2"
+    "open": "11.0.4"

The layer that actually stops the published payload is the dependency bump. open 11.0.0, released November 15, 2025 with commit 274d704, wraps the target in single quotes, and PowerShell does not expand anything inside single quotes. That fix had existed for ten months while OpenCode stayed pinned to 10.1.2. OpenCode added the vulnerable await open(authorizationUrl) call three weeks after the fixed open shipped.

The two scheme checks are defense in depth, and honest scoping matters: the payload above is https://, so both new checks pass it. Without the open bump, the fix would have looked complete and the hole would have stayed open.

The fix is complete for this path: every browser-launch call site now goes through openUrl, and the OAuth boundary itself rejects non-http(s) endpoints. There is no advisory and no CVE. Anomaly's position, stated when asked, is that a malicious MCP server is outside its threat model, so the change shipped described only as a refactor that shares one browser opener across the opencode and tui packages. The diff is public, but scanners that match versions against published advisories have no record for this flaw.

How do I check if I was compromised?

Pillar Security reported no signs that the flaw has been used in attacks and published no indicators. On Windows and WSL machines that ran affected versions with remote MCP servers configured:

  • Run opencode mcp list and review every remote server for provenance: who publishes it, where it is registered, and whether its domain or registry entry could have changed hands since it was configured.
  • Treat any unexplained process or file creation around an MCP sign-in, on a machine running 1.0.137 to 1.18.32, as grounds for the standard response to developer-machine compromise: rotate every credential, token and key reachable from that machine, and rebuild the environment from a known-good state.
  • Note the separate incident: a breach of OpenCode's hosted Console disclosed around the same time may have exposed some users' stored API keys for OpenAI, Anthropic and Google. That is a different event from this in-client flaw.

Timeline

  • 2020-10-22: open issue #202 reports a URL with $ losing characters on WSL 2, filed as a broken link, five years before it was understood as PowerShell string expansion.
  • 2021-09-17: the open README adds its standing disclaimer that the package makes no security guarantees and that sanitizing untrusted input is the caller's job.
  • 2023-07-24: open issues #318 and #319 cover PowerShell argument escaping.
  • 2023-10-18: open issue #323 reports OS command injection on Windows when opening URLs, with a one-line proof of concept; it is labeled an enhancement and later closed not planned.
  • 2025-07-09: CVE-2025-6514, the same defect in the mcp-remote proxy, rated 9.6, published with JFrog's analysis naming open() as the sink.
  • 2025-08-28 and 2025-09-17: CVE-2025-58062 in openmcp-client and CVE-2025-10619 in sequa-mcp map the same sink.
  • 2025-11-15: open 11.0.0 ships commit 274d704, fixing the PowerShell argument escaping, and the release notes list it under Fixes.
  • 2025-12-07: OpenCode commit 509e43d6 adds await open(authorizationUrl) to the MCP OAuth path, pinning open 10.1.2, three weeks after the fixed open shipped.
  • 2026-08-29: Pillar Security reports the flaw privately through a GitHub Security Advisory with full reproduction material.
  • 2026-09-25: pull request #51414 merges as commit 29f07e0c, described as a refactor; Anomaly confirms the report reached the team and replies that malicious MCP servers are outside its threat model.
  • 2026-09-28: OpenCode 1.18.33 publishes to npm with the fix.
  • 2026-10-19: Pillar Security publishes the disclosure, having asked Anomaly to reconsider and request a CVE.
  • 2026-10-26: this post.

FAQ

Why will my scanner not flag this vulnerability?

There is no CVE and no vendor advisory, because Anomaly did not request either after fixing the flaw, so vulnerability databases have nothing to match against. The affected range is known only from the fix that shipped as a refactor and from Pillar's disclosure. Finding affected installs means checking npm package versions with npm ls -g opencode-ai @opencode/cli and reviewing configured MCP servers with opencode mcp list, rather than relying on an advisory-based scanner.

Does URL scheme validation stop this attack?

No. The published payload is an ordinary https:// URL. It passes the MCP TypeScript SDK's denylist of javascript:, data: and vbscript:, and the two scheme checks OpenCode added in 1.18.33. It would also pass the stricter http/https allowlist the SDK considered but never shipped. What actually closed the hole was upgrading open to 11.0.0 or later (OpenCode bumped to 11.0.4), which single-quotes the target so PowerShell does not expand it.

Are macOS and Linux installs affected?

No. On those platforms open launches the browser through spawn with an argument vector, so no shell command is built and no string is interpolated. Only Windows and WSL builds take the PowerShell path.

Is this the same as the OpenCode Console breach in the news?

No. The Console breach was a separate incident disclosed around the same time, in which stored API keys for OpenAI, Anthropic and Google may have been exposed. This flaw runs code on the developer's machine through an MCP sign-in and needs no breach of OpenCode's own systems.

Which MCP clients handle this correctly?

Pillar's review identified several approaches:

  • gemini-cli uses a scheme-gated execFile opener with no shell.
  • Claude Code guards the opener so the OAuth path cannot reach it with a server-controlled value.
  • github-mcp-server takes authorization endpoints from the operator instead of discovering them from the peer.
  • Docker mcp-gateway validates every OAuth endpoint behind an https allowlist with an argv opener.

These approaches keep a remote server's string from becoming shell syntax.

What if I cannot upgrade right now?

Remove every remote MCP server you have not fully vetted from the OpenCode configuration on Windows and WSL machines, and re-check with opencode mcp list. A local MCP server that runs code you installed yourself is outside this flaw's path, which needs a remote server's OAuth handshake.

Scan for the unpatched OpenCode range

Review your repositories with Ostorlab Source Code Security Testing. On developer machines and inside CI images, run npm ls -g opencode-ai @opencode/cli and check for opencode-ai 1.0.137–1.18.32 or @opencode/cli 2.0.0–2.0.17. These version checks identify the affected ranges without waiting for an advisory-based alert.

References

  • Pillar Security, "Open and Shut: Remote Code Execution in opencode via MCP OAuth", October 19, 2026, with the root cause, the quoted open 10.1.2 source, the proof of concept, the affected matrix and the disclosure timeline: https://www.pillar.security/blog/open-and-shut-remote-code-execution-in-opencode-via-mcp-oauth (checked October 26, 2026)
  • The OpenCode repository, anomalyco/opencode: https://github.com/anomalyco/opencode (checked October 26, 2026)
  • Vulnerable commit 509e43d6, adding await open(authorizationUrl) on December 7, 2025: https://github.com/anomalyco/opencode/commit/509e43d6 (referenced via Pillar Security, October 26, 2026)
  • Fix, pull request #51414, merged as commit 29f07e0c on September 25, 2026: https://github.com/anomalyco/opencode/pull/51414 and https://github.com/anomalyco/opencode/commit/29f07e0c (checked October 26, 2026)
  • OpenCode 1.18.33 on npm, published September 28, 2026, and the version timestamps confirming 1.0.137 (December 9, 2025) through 1.18.32 (September 21, 2026) as the affected line: https://www.npmjs.com/package/opencode-ai/v/1.18.33 and https://registry.npmjs.org/opencode-ai (checked October 26, 2026)
  • The v2 fix, PR #51412, and release-tag manifests confirming the dependency change from open 10.1.2 in v2.0.17 to 11.0.4 in v2.0.18: https://github.com/anomalyco/opencode/pull/51412, https://github.com/anomalyco/opencode/blob/v2.0.17/packages/cli/package.json and https://github.com/anomalyco/opencode/blob/v2.0.18/packages/util/package.json; npm publication timestamps: https://registry.npmjs.org/@opencode/cli (checked October 28, 2026)
  • The open package at 10.1.2, the pinned version's Windows path: https://github.com/sindresorhus/open/tree/v10.1.2 (checked October 26, 2026)
  • open issue #202, a URL with $ on WSL 2, October 22, 2020; issue #323, OS command injection on Windows, October 18, 2023; and the fix commit 274d704 in open 11.0.0, November 15, 2025: https://github.com/sindresorhus/open/issues/202, https://github.com/sindresorhus/open/issues/323 and https://github.com/sindresorhus/open/commit/274d704 (checked October 26, 2026)
  • CVE-2025-6514, the same sink in mcp-remote, CVSS v3.1 9.6, with JFrog's analysis: https://nvd.nist.gov/vuln/detail/CVE-2025-6514 and https://jfrog.com/blog/2025-6514-critical-mcp-remote-rce-vulnerability/ (referenced via Pillar Security, October 26, 2026)
  • CVE-2025-58062 in openmcp-client and CVE-2025-10619 in sequa-mcp, the same sink: https://nvd.nist.gov/vuln/detail/CVE-2025-58062 and https://nvd.nist.gov/vuln/detail/CVE-2025-10619 (referenced via Pillar Security, October 26, 2026)
  • The Hacker News on the OpenCode flaw, October 22, 2026: https://thehackernews.com/2026/10/opencode-flaw-lets-malicious-mcp.html (checked October 26, 2026)
  • The Hacker News on the separate OpenCode Console breach: https://thehackernews.com/2026/10/opencode-console-breach-may-have.html (referenced via the OpenCode article, October 26, 2026)
  • npm weekly downloads for opencode-ai, 2,180,172 for the week to October 22, 2026, and for @opencode/cli, 321,091: https://api.npmjs.org/downloads/point/2026-10-16:2026-10-22/opencode-ai and https://api.npmjs.org/downloads/point/last-week/@opencode/cli (checked October 26, 2026)
  • npm weekly downloads for the open package, 102,012,551 for the week to October 22, 2026: https://api.npmjs.org/downloads/point/last-week/open (checked October 26, 2026)
  • GitHub star count for anomalyco/opencode, 221,765 as of October 26, 2026: https://api.github.com/repos/anomalyco/opencode (checked October 26, 2026)