OpenCode flaw: a malicious MCP server's URL runs code

$ cat blog/opencode-mcp-oauth-command-injection.md
✓ incident
✓ MCP
✓ command injection
✓ AI security
5 min read
$ 

On October 22, 2026, The Hacker News reported a critical command injection flaw in OpenCode, the AI coding agent from Anomaly. On Windows and WSL, a malicious remote MCP server could run commands on a developer's machine during OAuth sign-in, using nothing more exotic than an ordinary HTTPS URL. Read the original report.

The bug is a textbook shell injection, but the input arrived through a channel many teams still treat as trusted: the MCP server.

What happened

According to the report, the flaw affects OpenCode on Windows and WSL only. It has no CVE; Pillar Security rates it critical, with a proposed score of 9.8. It was reported privately on August 29 and disclosed on October 19.

  • Affected: versions 1.0.137 through 1.18.32.
  • Fixed: 1.18.33, released September 28. The fix was folded into a pull request described as a refactor of browser-launch code.
  • No known exploitation, per the report.

The mechanics: during OAuth sign-in, the MCP server supplies the authorization endpoint URL. On Windows and WSL, OpenCode passed that URL to the open helper package, which built a PowerShell command around the URL without escaping it. A URL containing PowerShell syntax therefore ran that syntax as a command.

A check that the URL uses HTTP or HTTPS didn't help, because the malicious URL was ordinary HTTPS. The open package's Windows command injection risk had been reported in 2023 and fixed in version 11, but OpenCode stayed on an older release.

The preconditions are real but not exotic. The attacker must control a remote MCP server the developer has added, which could be a server taken over later or a domain that changes hands. The developer must also run opencode mcp auth. Anomaly said user-configured MCP servers are "outside our trust boundary" and declined to request a CVE.

Why it worked

Shell injection happens whenever a program builds a single command string that a shell will parse, and part of that string comes from someone else. The shell can't tell which characters were meant as data. Quotes, semicolons, backticks and subexpressions in the data become instructions.

Here is an illustrative example in Node.js, not OpenCode's or the open package's code:

// Vulnerable: the URL is pasted into a string that PowerShell parses
import { exec } from "node:child_process";

function openInBrowser(url: string) {
  exec(`powershell -NoProfile -Command Start-Process '${url}'`);
}

A URL such as https://example.test/auth?x=');calc;(' closes the quote, runs calc, and reopens it, and it is still a well-formed HTTPS URL. The safer shape avoids a shell entirely and passes the URL as one argument:

// Safer: parse the URL, then hand it to a process as a single argument, no shell
import { execFile } from "node:child_process";

function openInBrowser(raw: string) {
  const url = new URL(raw);
  if (url.protocol !== "https:") throw new Error("refusing non-https URL");
  execFile("explorer.exe", [url.href], { shell: false });
}

In practice, prefer a current, maintained launcher library that does this for you and keep it up to date. The point is the shape: arguments as a list, no command string, no shell.

URL validation is not shell safety

Scheme and host checks answer "is this a web address I'm willing to visit?". They don't answer "is this string safe to embed in PowerShell, bash or cmd?". Those are different grammars with different special characters. Validate URLs with a URL parser for the first question, and avoid building shell strings for the second. Don't try to escape your way out with a hand-written function; quoting rules on Windows in particular are easy to get wrong.

MCP servers are untrusted input

An MCP server you connect to is a remote party. Everything it returns, from tool descriptions to sign-in URLs, is input your client has to treat as hostile. Our posts on MCP server security and securing AI agent setups go deeper on that trust boundary.

attack, as reported where OpenRouting checks 1 Developer adds a remote MCP server✓ Agent setup review (MCP) 2 Developer runs opencode mcp authlocal CLI, not a repo file 3 Server supplies a crafted auth URL✓ AI/LLM safety agent 4 URL wrapped in a PowerShell command✓ Semgrep command injection 5 PowerShell syntax runs locally✓ Command injection agent
The attack as reported, and where an OpenRouting check applies. Illustrative.

What to do now

  1. Upgrade OpenCode to 1.18.33 or later on every Windows and WSL machine that runs it.
  2. If you can't upgrade yet, remove any remote MCP servers you haven't vetted. List them with opencode mcp list.
  3. Review which remote MCP servers your team adds, who controls each domain, and whether that could change. A server that is safe today can change hands.
  4. In your own tools, search for exec, spawn with shell: true, subprocess with shell=True, os.system and PowerShell -Command strings that include outside input, and switch them to argument lists.
  5. Keep helper libraries that launch processes up to date; a fixed upstream release only helps if you actually move to it.

How OpenRouting helps

OpenRouting checks the two sides of this in your own repositories: the code that builds commands, and the agent setup that decides which servers you trust.

  • Semgrep rules flag command injection patterns, such as exec with an interpolated string, shell=True with variable input, or os.system on concatenated text.
  • The command injection agent reviews each finding with the surrounding code, decides whether the input is really attacker-controlled (a value from an MCP server, a URL from a response), and suggests an argument-list fix. Triage runs on Claude, via Amazon Bedrock.
  • The agent setup review inventories the AI agent setup committed to your repository, including MCP configs such as .mcp.json, and reviews it for prompt injection, unsafe tools and exposed secrets. A remote server you don't recognise is something to question.
  • The AI/LLM safety agent reviews LLM application code flagged by the scanners, such as model or tool output handed to a shell, a browser launcher or a file write.

What it won't catch

  • OpenRouting doesn't do dependency or SCA scanning yet, so it won't tell you that a developer runs OpenCode 1.18.32 or that a project depends on an old open release. Upgrade through your package manager and the vendor advisory.
  • It reviews MCP configs that are committed to a repository. Servers a developer adds only on their own machine aren't visible to a repository scan.
  • It can't tell whether a remote MCP server is malicious today, or whether its domain will change hands.
  • Findings come from a scan you run, manually or as a PR review.

Sources

Questions

What was the OpenCode MCP OAuth flaw?

On Windows and WSL, OpenCode passed the authorization URL that an MCP server supplies during OAuth sign-in to the open helper package, which built a PowerShell command around it without escaping. A malicious remote MCP server could therefore run commands on a developer's machine using an ordinary HTTPS URL.

Which OpenCode versions are affected?

Versions 1.0.137 through 1.18.32 on Windows and WSL are affected, and 1.18.33, released September 28, fixes it. The flaw has no CVE, and the report lists no known exploitation.

Why didn't checking for https:// stop the OpenCode attack?

The malicious URL was ordinary HTTPS. A scheme check answers whether a string is a web address, not whether it is safe to embed in PowerShell, bash or cmd; the safe shape passes arguments to a process as a list, with no shell.

What should I do if I can't upgrade OpenCode yet?

Remove any remote MCP servers you haven't vetted; you can list them with opencode mcp list. Review who controls each server's domain, because a server that is safe today can change hands.

Keep reading