CVE
Not assigned
CWE
CWE-78, CWE-88, CWE-346, CWE-350
Affected Surface
- npm package `opencode-ai` (OpenCode CLI and TUI) versions `1.0.137` through `1.18.32` on Windows and WSL when connecting or signing in to a remote MCP server (fixed in `1.18.33`, no CVE or vendor advisory issued)
- npm package `@modelcontextprotocol/inspector` versions `2.0.0` through `2.11.0` on Windows and WSL via OAuth `authorization_endpoint` PowerShell subexpression evaluation (`GHSA-q978-78c7-phqw`, fixed in `2.12.0`)
- npm packages `@modelcontextprotocol/inspector` and `@modelcontextprotocol/inspector-client` versions `0.13.0` through `1.0.2` on all operating systems via OAuth callback `transport=stdio` parameter injection (`GHSA-4fc4-wcmh-4g86`, fixed in `1.0.3`)
- npm packages `@modelcontextprotocol/inspector` and `@modelcontextprotocol/inspector-server` versions `0.14.1` through `1.0.2` and `2.0.0` through `2.11.0` across companion 21 October advisories (`GHSA-vcc9-3xgm-p9v4`, `GHSA-wmjf-2m46-2352`, `GHSA-f6mc-jg3x-vq53`, `GHSA-m5pj-3536-3xcp`)
- Any Node.js CLI or agent tool on Windows or WSL that passes a remote peer's URL to `open` `< 11.0.0`, even when guarded by an `http:` / `https:` protocol check
TL;DR
Connecting an AI coding agent or MCP debugging client to a remote Model Context Protocol (MCP) server over HTTP does not just expose the model to untrusted tool output. Before any tool is listed or any prompt is sent, an HTTP MCP server can answer 401 Unauthorized and hand the client an OAuth discovery document whose authorization_endpoint is an attacker-chosen string.
Between 19 and 22 October 2026, Pillar Security and the maintainers of @modelcontextprotocol/inspector disclosed that two widely used MCP developer tools passed that authorization_endpoint straight into open@10.x.
In OpenCode (opencode-ai on npm, ~3 million weekly downloads), versions 1.0.137 through 1.18.32 on Windows and WSL execute arbitrary PowerShell commands embedded as $(...) inside an ordinary https:// authorization URL when a developer runs opencode mcp auth or signs in to a remote MCP server. Anomaly fixed the flaw in 1.18.33 (commit 29f07e0c) by upgrading open from 10.1.2 to 11.0.4, but shipped the change as a browser-opener refactor without a CVE or security advisory. Standard SCA scanners will not alert on vulnerable installs.
In MCP Inspector (@modelcontextprotocol/inspector on npm), the maintainers published six security advisories on 21 October 2026, fixed in 2.12.0 (for the v2 line) and 1.0.3 (for the v1 line). Two give a remote MCP server immediate code execution as the developer: GHSA-q978-78c7-phqw (CVSS 8.8) is the same open@10.2.0 PowerShell subexpression injection on Windows and WSL, and GHSA-4fc4-wcmh-4g86 (CVSS 9.6) lets an OAuth /authorize redirect append ?transport=stdio&serverCommand=... to /oauth/callback, switching a remote connection into a local stdio process spawn on macOS, Linux, and Windows.
Upgrade opencode-ai to 1.18.33 or later (1.19.4 is current) and @modelcontextprotocol/inspector to 2.12.0 or 1.0.3. Because OpenCode has no CVE for this flaw, grep lockfiles and check developer workstations directly with opencode --version.
Affected packages and projects
| Ecosystem | Package | Advisory / identifier | Affected versions | Fixed or safe version | Platform / condition |
|---|---|---|---|---|---|
| npm | opencode-ai | No CVE / no GHSA (Pillar Security report, commit 29f07e0c) | >= 1.0.137, <= 1.18.32 | 1.18.33 (1.19.4 latest) | Windows and WSL when initiating OAuth sign-in to a remote MCP server |
| npm | @modelcontextprotocol/inspector | GHSA-q978-78c7-phqw (CVSS 8.8) | >= 2.0.0, <= 2.11.0 | 2.12.0 | Windows and WSL when connecting to a remote MCP server that returns 401 OAuth discovery |
| npm | @modelcontextprotocol/inspector, @modelcontextprotocol/inspector-client | GHSA-4fc4-wcmh-4g86 (CVSS 9.6) | >= 0.13.0, <= 1.0.2 | 1.0.3 (2.12.0 on v2) | All OSes when completing OAuth redirect to /oauth/callback |
| npm | @modelcontextprotocol/inspector | GHSA-vcc9-3xgm-p9v4 (High) | >= 2.0.0, <= 2.11.0 | 2.12.0 | All OSes; DNS rebinding steals window.__INSPECTOR_API_TOKEN__ and /api/storage/oauth secrets |
| npm | @modelcontextprotocol/inspector, @modelcontextprotocol/inspector-server | GHSA-wmjf-2m46-2352 (CVSS 7.5) | >= 0.14.1, <= 1.0.2 and 2.0.0 to 2.11.0 | 1.0.3, 2.12.0 | All OSes when started with DANGEROUSLY_OMIT_AUTH=true; absent Origin header spawns local stdio processes |
| npm | @modelcontextprotocol/inspector, @modelcontextprotocol/inspector-server | GHSA-f6mc-jg3x-vq53 (Medium) | >= 1.0.0, <= 1.0.2 | 1.0.3 | All OSes; /fetch manual redirect loop leaks Authorization and Cookie headers cross-origin |
| npm | @modelcontextprotocol/inspector, @modelcontextprotocol/inspector-server | GHSA-m5pj-3536-3xcp (Medium) | >= 0.21.2, <= 1.0.2 and >= 2.0.0, <= 2.11.0 | 1.0.3, 2.12.0 | All OSes; unbounded await res.text() in /api/fetch and /fetch allows memory exhaustion |
| npm | open (transitive sink) | Upstream commit 274d704 (issues #202, #318, #319, #323) | < 11.0.0 (when caller passes untrusted URLs) | 11.0.0 (11.0.4 latest) | Windows (win32) and WSL outside containers |
Technical analysis
1. Why https:// scheme validation fails against open@10.x on Windows and WSL
After CVE-2025-6514 hit mcp-remote in July 2025, many MCP clients and SDKs treated browser-launch command injection as a URL-scheme problem: reject javascript:, data:, file:, or non-HTTP schemes, allow http: and https:, and pass url.href to open().
Both Pillar Security’s report on OpenCode and GHSA-q978-78c7-phqw in MCP Inspector show why that control fails when the underlying opener is open 10.x (10.1.2 in OpenCode 1.18.32, 10.2.0 in MCP Inspector 2.11.0).
On macOS and native Linux, open launches open or xdg-open through child_process.spawn() with an argument array, so no shell interprets the URL. On Windows (process.platform === 'win32') and on WSL outside a container, open 10.2.0 (index.js lines 188 to 204) builds a PowerShell Start command, base64-encodes it as UTF-16LE, and runs powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand:
// sindresorhus/open v10.2.0 index.js
if (app) {
encodedArguments.push(`"\`"${app}\`""`);
}
...
} else if (options.target) {
encodedArguments.push(`"${options.target}"`);
}
options.target = Buffer.from(encodedArguments.join(' '), 'utf16le').toString('base64');
Notice the asymmetry between those two branches. The app binary path has its inner quotes backtick-escaped (\”), while options.target` is dropped directly inside double quotes:
Start "https://evil.test/$([IO.File]::WriteAllText('C:\Users\Public\pwned.txt','RCE'))"
In PowerShell, double-quoted strings are expandable strings. The parser evaluates every $(...) subexpression before it invokes Start. Even better for an attacker, WHATWG URL parsing (new URL(input).href) percent-encodes spaces and double quotes in the path, query, and fragment, but leaves every character needed for a PowerShell method call untouched:
- Preserved in path, query, and fragment:
$,(,),[,],:,,,+,& - Preserved in path and fragment:
'(single quote)
Because [IO.File]::WriteAllText('C:\Users\Public\pwned.txt','RCE') contains no spaces and no double quotes, new URL() parses it as a valid https: URL and serializes it unchanged. Every scheme filter waves it through:
- The MCP TypeScript SDK’s client denylist (blocking
javascript:,data:, andvbscript:) allows it. - OpenCode’s new
openUrl()check (url.protocol !== "http:" && url.protocol !== "https:") allows it. - MCP Inspector’s
OPENABLE_PROTOCOLSallowlist (new Set(["http:", "https:"])) allows it.
In OpenCode 1.18.32, the path from the remote server’s JSON response to PowerShell ran through packages/opencode/src/mcp/oauth-provider.ts and packages/opencode/src/mcp/browser.ts. In MCP Inspector 2.11.0, the same discovery field flowed from core/auth/discovery.ts and core/auth/providers.ts into clients/cli/src/cli-oauth-navigation.ts and three separate openUrl() helpers (clients/cli/src/open-url.ts, clients/tui/src/utils/openUrl.ts, and clients/web/server/web-server-config.ts):
// modelcontextprotocol/inspector @ edf54f5de: clients/cli/src/open-url.ts
export async function openUrl(url: string | URL): Promise<void> {
await open(typeof url === "string" ? url : url.href);
}
In MCP Inspector’s CLI, auto-opening the browser is enabled whenever VITEST is unset and MCP_AUTO_OPEN_ENABLED is not "false". Pointing npx @modelcontextprotocol/inspector --cli at an untrusted server on Windows or WSL fires the chain on the first 401 response without a prompt.
2. How open@11.0.4, OpenCode 1.18.33, and MCP Inspector 2.12.0 close the sink
Upstream sindresorhus/open fixed the PowerShell target escaping in version 11.0.0 (commit 274d704, November 2025) by wrapping arguments in single quotes via executePowerShell.escapeArgument(options.target) and doubling any embedded single quotes. PowerShell treats single-quoted strings as verbatim literals and does not evaluate $(...).
OpenCode’s fix in PR #51414 (commit 29f07e0c, released in 1.18.33) upgrades open from 10.1.2 to 11.0.4, centralizes browser launches in packages/core/src/open.ts, and checks the protocol in both openUrl() and McpOAuthProvider.redirectToAuthorization():
--- a/packages/opencode/src/mcp/oauth-provider.ts
+++ b/packages/opencode/src/mcp/oauth-provider.ts
@@ -125,6 +125,10 @@ export class McpOAuthProvider implements OAuthClientProvider {
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)
}
MCP Inspector 2.12.0 (commit 73f3cf8e23) likewise upgrades open from ^10.2.0 to ^11.0.4 across the root workspace and clients/web, and rewrites core/node/openUrl.ts so the caller always opens the normalized parsed.href returned by new URL(href) rather than the raw input string:
// modelcontextprotocol/inspector commit 73f3cf8e23: core/node/openUrl.ts
const OPENABLE_PROTOCOLS: ReadonlySet<string> = new Set(["http:", "https:"]);
function toOpenableHref(href: string): string {
let parsed: URL;
try {
parsed = new URL(href);
} catch {
throw new Error("refusing to open an invalid URL");
}
if (!OPENABLE_PROTOCOLS.has(parsed.protocol)) {
throw new Error(`refusing to open a non-http(s) URL (${parsed.protocol})`);
}
return parsed.href;
}
3. GHSA-4fc4-wcmh-4g86: OAuth callback query pollution switches remote HTTP to local stdio RCE
While GHSA-q978-78c7-phqw only affects Windows and WSL, GHSA-4fc4-wcmh-4g86 (CVSS 9.6 Critical) hits @modelcontextprotocol/inspector 0.13.0 through 1.0.2 on every operating system.
When a developer connects MCP Inspector to a remote server that requires OAuth, the remote authorization server redirects the user’s browser back to Inspector’s local callback route (http://127.0.0.1:6274/oauth/callback). An attacker-controlled authorization server can preserve the legitimate code and state parameters while appending three Inspector UI query parameters to that redirect:
http://127.0.0.1:6274/oauth/callback?code=evil-code&state=<valid-state>&transport=stdio&serverCommand=calc.exe&serverArgs=
When /oauth/callback loads in the browser, client/src/App.tsx initializes the React state for the connection directly from window.location.search:
// Vulnerable initialization in client/src/App.tsx (<= 1.0.2)
const [command, setCommand] = useState<string>(getInitialCommand);
const [args, setArgs] = useState<string>(getInitialArgs);
const [transportType, setTransportType] = useState<
"stdio" | "sse" | "streamable-http"
>(getInitialTransportType);
OAuthCallback.tsx then extracts params.code, finishes the OAuth token exchange, and calls onConnect(serverUrl) to reconnect automatically. Because transportType was overwritten with "stdio" and command was overwritten with serverCommand from the callback URL, connectMcpServer() sends an authenticated request (carrying the browser’s valid proxy session token) to the local proxy’s /stdio endpoint, which spawns StdioClientTransport with the attacker’s command and arguments.
The v1.0.3 fix (commit 0d5c0469b1) stops reading connection parameters from the URL on callback routes and snapshots the active connection in per-tab sessionStorage before redirecting:
--- a/client/src/utils/configUtils.ts
+++ b/client/src/utils/configUtils.ts
@@ -3,8 +3,15 @@
+import { isOAuthCallbackPath } from "@/lib/oauthConnectionSnapshot";
+
const getSearchParam = (key: string): string | null => {
+ if (isOAuthCallbackPath()) return null;
try {
const url = new URL(window.location.href);
return url.searchParams.get(key);
4. DNS rebinding and proxy bypasses in the 21 October MCP Inspector batch
Four other advisories in the 21 October MCP Inspector release close gaps in the local web server and proxy that sit between the browser and the developer’s machine.
Under GHSA-vcc9-3xgm-p9v4 (affecting 2.0.0 through 2.11.0), the v2 production Hono server injected window.__INSPECTOR_API_TOKEN__ into the HTML returned by GET / and the SPA fallback without checking the Host header, while originValidationMiddleware allowed requests with no Origin header (which browsers omit on same-origin GET requests). After DNS rebinding evil[.]com:6274 to 127.0.0.1, attacker JavaScript could fetch("http://evil[.]com:6274/") to read the API token and then call GET /api/storage/oauth and GET /api/import-source to read stored OAuth tokens and local config files. Version 2.12.0 (commit 123998321d) fixes this by placing hostAllowListMiddleware in front of every route except /healthz.
Under GHSA-wmjf-2m46-2352, setting DANGEROUSLY_OMIT_AUTH=true disabled token checks while the remaining Origin middleware still allowed requests with no Origin header, so any local or network-reachable non-browser client could POST /api/mcp/connect (v2) or GET /stdio (v1) to spawn commands. Versions 1.0.3 and 2.12.0 (commit e9cd45b987) require an allowed Origin header whenever auth is disabled and restrict DANGEROUSLY_* flags to explicit true, 1, or yes values.
Finally, GHSA-f6mc-jg3x-vq53 and GHSA-m5pj-3536-3xcp cover the /fetch and /api/fetch proxy routes. A manual redirect loop added for SSRF protection in 1.0.0 reused the initial headers object on every hop, forwarding Authorization and Cookie headers across cross-origin 301/302 redirects (commit b2e41c1680 strips sensitive headers on cross-origin or scheme-downgrading hops). Both /fetch (v1) and POST /api/fetch (v2) also buffered upstream responses with an uncapped await res.text(), fixed in commit 4862b74609 by enforcing a streaming byte budget (readCappedText).
Detection and remediation
1. Check installed OpenCode and MCP Inspector versions
Because Anomaly did not assign a CVE or publish a GHSA for the OpenCode MCP OAuth bug, dependency scanners will not flag opencode-ai 1.0.137 through 1.18.32. Check developer machines, WSL distributions, and project lockfiles directly:
# Check global or active OpenCode CLI version
opencode --version 2>/dev/null || true
# List configured MCP servers in OpenCode
opencode mcp list 2>/dev/null || true
# Search lockfiles and package manifests for vulnerable opencode-ai and @modelcontextprotocol/inspector versions
rg -n '"(opencode-ai|@modelcontextprotocol/inspector(-(client|server))?)"\s*:\s*"[^"]*"' \
package.json package-lock.json pnpm-lock.yaml yarn.lock bun.lock 2>/dev/null
Also check whether npx caches on developer workstations hold older @modelcontextprotocol/inspector versions (<= 2.11.0 or <= 1.0.2), since developers commonly launch Inspector via an unpinned npx @modelcontextprotocol/inspector:
find ~/.npm/_npx -name 'package.json' -exec grep -H '"@modelcontextprotocol/inspector"' {} + 2>/dev/null
2. Audit your own code for open < 11.0.0 on remote URLs
Any Node.js CLI, agent, or desktop wrapper that passes a server-supplied URL to open < 11.0.0 on Windows or WSL has the same PowerShell subexpression sink, even if it validates parsed.protocol === "https:":
npm ls open 2>/dev/null || pnpm why open 2>/dev/null || true
If your code opens URLs received from remote servers (such as OAuth authorization_endpoint, OIDC discovery metadata, or webhook links):
- Upgrade
opento^11.0.4, which single-quotes targets inexecutePowerShell.escapeArgument(). - Parse the input with
new URL(), enforce anhttps:(or loopbackhttp:) allowlist, and pass the serializedparsed.hrefrather than the raw input string. - Where possible, avoid shell-backed launchers altogether or percent-encode
$before handing the URL to an external opener.
3. Upgrade and rotate credentials if exposed
- Upgrade
opencode-aito1.18.33or later (npm install -g opencode-ai@latest) on every Windows and WSL workstation. If you cannot upgrade immediately, remove unvetted remote HTTP MCP servers from your OpenCode configuration (~/.config/opencode/or project.opencode/). - Upgrade
@modelcontextprotocol/inspectorto2.12.0(or1.0.3if pinned to the v1 line), clear stale~/.npm/_npxcache entries, and stop running Inspector withDANGEROUSLY_OMIT_AUTH=true. - If a developer connected OpenCode (
<= 1.18.32on Windows/WSL) or MCP Inspector (<= 2.11.0/<= 1.0.2) to an untrusted or unexpected remote MCP server, audit Windows PowerShell operational logs (Event ID4104/4688forpowershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand) and rotate any OAuth tokens, cloud credentials, and API keys stored on that workstation.
Timeline
- 2020-10-22:
sindresorhus/openissue#202reports that URLs containing$break on WSL 2 because PowerShell expands$inside double quotes. - 2023-10-18:
sindresorhus/openissue#323demonstratesopen('https://$(calc.exe)')as OS command injection on Windows. - 2025-07-09:
CVE-2025-6514(CVSS 9.6) is disclosed inmcp-remotefor the same OAuthauthorization_endpointtoopen()sink. - 2025-11-15:
open11.0.0(commit274d704) ships single-quoted PowerShell argument escaping viaexecutePowerShell.escapeArgument(). - 2025-12-07: OpenCode adds MCP OAuth browser launching while pinned to
open@10.1.2. - 2026-08-29: Pillar Security privately reports the Windows/WSL MCP OAuth RCE to Anomaly via a GitHub Security Advisory.
- 2026-09-25: Anomaly merges PR
#51414(commit29f07e0c), upgradingopento11.0.4and addingopenUrl()scheme checks, while stating that malicious remote MCP servers are outside its threat model. - 2026-09-28: OpenCode
1.18.33is published to npm without a security advisory or CVE. - 2026-10-19 to 2026-10-22: Pillar Security publishes its technical disclosure and The Hacker News and Infosecurity Magazine report on the unindexed flaw.
- 2026-10-21:
@modelcontextprotocol/inspectorreleases versions2.12.0and1.0.3and publishes six security advisories (GHSA-q978-78c7-phqw,GHSA-4fc4-wcmh-4g86,GHSA-vcc9-3xgm-p9v4,GHSA-wmjf-2m46-2352,GHSA-f6mc-jg3x-vq53, andGHSA-m5pj-3536-3xcp).
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.
References
- Pillar Security: Open and Shut, remote code execution in opencode via MCP OAuth
- MCP Inspector advisory GHSA-q978-78c7-phqw (OAuth URL PowerShell subexpression injection)
- MCP Inspector advisory GHSA-4fc4-wcmh-4g86 (OAuth callback transport switch to stdio RCE)
- MCP Inspector advisory GHSA-vcc9-3xgm-p9v4 (DNS rebinding API and OAuth token theft)
- MCP Inspector advisory GHSA-wmjf-2m46-2352 (absent-Origin fail-open with DANGEROUSLY_OMIT_AUTH)
- OpenCode commit 29f07e0c (PR #51414): share a browser opener and bump open to 11.0.4
- MCP Inspector commit 73f3cf8e23: bump open to v11 and restrict openUrl to http(s)
- MCP Inspector commit 0d5c0469b1: don't take connection settings from the OAuth callback URL
- The Hacker News: OpenCode flaw lets malicious MCP servers execute code during sign-in on Windows and WSL