Skip to content

fix(login): avoid cmd.exe when opening the login URL on Windows (#1114)#1116

Open
maxmilian wants to merge 2 commits into
colbymchenry:mainfrom
maxmilian:fix/1114-openbrowser-cmd-injection
Open

fix(login): avoid cmd.exe when opening the login URL on Windows (#1114)#1116
maxmilian wants to merge 2 commits into
colbymchenry:mainfrom
maxmilian:fix/1114-openbrowser-cmd-injection

Conversation

@maxmilian

@maxmilian maxmilian commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Closes #1114.

Problem

openBrowser() (src/reasoning/login.ts) launches the device-login verification URL that comes from the login server response (verification_uri_complete, where the server base is CODEGRAPH_LOGIN_URL / default app.getcodegraph.com). On Windows it ran:

: process.platform === "win32" ? ["cmd", ["/c", "start", "", url]]

cmd.exe (and its start builtin) re-parse shell metacharacters (& | ^ "). A malicious or compromised login server — or a user tricked into pointing CODEGRAPH_LOGIN_URL at an attacker — could return a verification_uri_complete such as https://app.getcodegraph.com/device?...&calc and get arbitrary commands executed. (spawn without shell: true does not help here: libuv only quotes args containing whitespace/quotes, so a space-free URL reaches cmd.exe unescaped.) Defense-in-depth (low): it needs a hostile server/env, but the browser-open path should never route a server-supplied string through a shell.

Fix

Two layers:

  1. Launch via rundll32 url.dll,FileProtocolHandler <url> instead of cmd /c start. rundll32 receives the URL as a single opaque argv element (Node spawn without shell), so there is no shell re-parsing. The macOS (open) and Linux (xdg-open) branches already pass the URL as a lone argument and are unchanged.
  2. https?:// scheme allowlist before launching anything. All three OS handlers (open / rundll32,FileProtocolHandler / xdg-open) will happily execute a file:///...exe, bare, or UNC (\\host\share\payload.exe) path handed to them — swapping the launcher alone doesn't close that, the allowlist does, on every platform.

Tests

__tests__/open-browser-security.test.ts: stubs process.platform = "win32", mocks child_process.spawn, and asserts (a) a URL containing & is not launched through cmd//c, arrives at the launcher as one intact argument, and never with shell: true; (b) file:/UNC/bare-path/javascript: targets are refused (no spawn at all); (c) plain http:// still launches. Written test-first (fails on the old cmd path, passes after the fix). vitest run + tsc --noEmit green.

Reachability note (please weigh in)

As of current main, openBrowser — and all of src/reasoning/login.ts — has no callers: e5897d0 removed the codegraph login / logout / usage command wiring and left the module orphaned. So on main this is hardening dead code (the vulnerable path does ship in already-released versions that had the command). If you'd rather delete login.ts outright than patch it, happy to rework this PR into the removal instead.

I develop on macOS, so I could not exercise the rundll32 path on a real Windows host. openBrowser is best-effort (errors are swallowed), so a Windows regression degrades gracefully rather than breaking login.

maxmilian added 2 commits July 2, 2026 07:48
…ymchenry#1114)

openBrowser() launches the device-login verification URL returned by the
login server. On Windows it ran the URL through `cmd /c start`, whose
parser (and the `start` builtin) re-interprets shell metacharacters
(& | ^ "). A malicious or compromised login server (or a CODEGRAPH_LOGIN_URL
override) could embed such characters to inject commands.

Launch via `rundll32 url.dll,FileProtocolHandler <url>` instead, which
receives the URL as a single opaque argument with no shell re-parsing.
macOS (open) and Linux (xdg-open) already pass the URL as a lone argv
element and are unaffected.
@maxmilian maxmilian marked this pull request as ready for review July 2, 2026 01:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security(low): openBrowser routes a server-supplied URL through Windows cmd /c start unescaped

1 participant