Skip to content

fix(desktop): fail WSL link opens closed on cmd.exe metacharacters - #126968

Open
dskwe wants to merge 2 commits into
NousResearch:mainfrom
dskwe:fix/wsl-external-open-cmd-injection-126939
Open

dskwe wants to merge 2 commits into
NousResearch:mainfrom
dskwe:fix/wsl-external-open-cmd-injection-126939

Conversation

@dskwe

@dskwe dskwe commented Sep 28, 2026 •

Copy link
Copy Markdown

What does this PR do?

Closes the cmd.exe command-separator hole on the WSL link-open path, and with it the silent open failure for any URL carrying & (i.e. most query strings).

The WSL path in external-open.ts spawns cmd.exe /c start "" <url>. Node's Windows argument escaping quotes whitespace and quotes only, so an & (or |, <, >, ^, %VAR%) inside the URL reached cmd.exe unquoted — reading as command-line syntax. Consequences: a URL like https://example.com/x&calc executes calc (command injection), and even a benign ?a=1&b=2 silently fails to open because everything after & becomes a second command.

This PR refuses URLs containing the cmd.exe metacharacter class before any spawn ({ ok: false, reason: 'invalid' }, no process, no notification), and quotes the URL that does reach cmd.exe. Raw \r\n is not constructible (the WHATWG URL parser strips tabs/newlines) but stays in the class as defense-in-depth. Metacharacter-free URLs — including mailto: with query strings — keep working.

Related Issue

Fixes #126939

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • apps/desktop/electron/external-open.ts: cmd.exe metacharacter class constant + pre-spawn refusal with log line; the spawned URL is double-quoted.
  • apps/desktop/electron/external-open.test.ts: happy-path test pins the quoted URL; new tests cover the refused metacharacter matrix (spawn count 0) and a metacharacter-free mailto: still spawning.

How to Test

  1. cd apps/desktop && npx vitest run electron/external-open.test.ts
  2. Manual (WSL): click a link with & in its query string; confirm nothing spawns and the open is refused; a plain URL opens normally.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run the targeted electron test suite (external-open.test.ts)
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: WSL behavior covered by unit tests; developed on macOS 14.0

Documentation & Housekeeping

  • I've updated relevant documentation — the metacharacter class comment documents why each character is refused
  • N/A — no new config keys
  • N/A — no architecture/workflow change
  • Cross-platform considered: the refusal applies to the WSL bridge path only (non-WSL platforms use openExternal untouched)
  • N/A — no tool behavior change

The WSL bridge handed the URL to cmd.exe as a bare spawn argument:
Node's Windows escaping quotes whitespace and quotes only, so an & in
a query string reached cmd.exe unquoted and parsed as a command
separator (arbitrary execution on the Windows host), while every such
URL already silently failed to open. Refuse URLs carrying cmd.exe
metacharacters before spawning (fail closed at the documented
chokepoint) and quote the URL explicitly for the ones that remain.
The hop itself stays: on WSL the shell.openExternal route resolves to
the Linux browser, cmd.exe start is how the Windows default browser is
reached.
Raw CRLF is stripped by the WHATWG URL parser before parsing, so it is
not constructible on this route; keep it in the character class as
defense-in-depth but out of the refusal assertions. Verified red on
upstream external-open.ts (3 failures) and green with the fix; the
whole electron vitest project for the three external-open files passes
and tsc/eslint/prettier are clean.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) platform/windows sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data needs-decision Awaiting maintainer decision before any implementation labels Sep 28, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related to #88558 and #124314, which address the same WSL cmd.exe launch path with different mechanisms. This variant fails closed by rejecting metacharacters, including ordinary ampersand query URLs; maintainer selection is needed rather than duplicate closure.

@alt-glitch alt-glitch added os/windows Native Windows behaviour or breakage: PowerShell install, Windows paths, cmd.exe, WSL-vs-native diff and removed platform/windows labels Oct 17, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) needs-decision Awaiting maintainer decision before any implementation os/windows Native Windows behaviour or breakage: PowerShell install, Windows paths, cmd.exe, WSL-vs-native diff P2 Medium — degraded but workaround exists sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] WSL external-link path hands a URL to cmd.exe with only a scheme allowlist, enabling &-delimited command execution

2 participants