Skip to content

[Plugin] Fix Windows OAuth sign-in: authorize URL truncated by cmd.exe (invalid_client) - #37

Merged
eshwar-sundar-glean merged 2 commits into
mainfrom
fix/win32-oauth-authorize-url-truncation
Jul 15, 2026
Merged

[Plugin] Fix Windows OAuth sign-in: authorize URL truncated by cmd.exe (invalid_client)#37
eshwar-sundar-glean merged 2 commits into
mainfrom
fix/win32-oauth-authorize-url-truncation

Conversation

@steve-calvert-glean

@steve-calvert-glean steve-calvert-glean commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

On Windows, the plugin's OAuth sign-in fails immediately with invalid_client / "The requested OAuth 2.0 Client does not exist", even though Dynamic Client Registration succeeds. Root cause is openBrowser() launching the authorize URL through cmd /c start.

Root cause

The MCP SDK builds the authorize URL with response_type first, then client_id and the rest, joined by &:

/oauth/authorize?response_type=code&client_id=...&redirect_uri=...&code_challenge=...&state=...

openBrowser() passed this to spawn("cmd", ["/c", "start", "", url]). Node only quotes an argv element on Windows if it contains a space/tab (or is empty); the URL has neither, so it reaches cmd.exe unquoted. cmd.exe treats & as a command separator, so it launches only:

start "" .../oauth/authorize?response_type=code

and everything after the first & (client_id, redirect_uri, PKCE, state) is dropped. The browser opens a URL with no client_id, and the server correctly rejects it as invalid_client.

Observed on an affected tenant: the load-balancer log shows the browser requesting exactly GET /oauth/authorize?response_type=code (truncated at the first &), immediately after a successful POST /oauth/register (201). macOS/Linux are unaffected because they use execFile("open"/"xdg-open", [url]), which passes the URL as a single argv element with no shell.

Fix

Eshwar - Prefer CMD for now as that was the only approach I was able to test

Launch the URL via explorer.exe instead of cmd /c start. explorer.exe opens the default browser and receives the URL as a single argv element with no shell, so & and percent-encoded characters survive intact.

Chose explorer.exe over rundll32 url.dll,FileProtocolHandler (also cmd-free) because rundll32 is a common LOLBin frequently blocked by enterprise EDR/AppLocker, and the affected users are on managed corporate Windows. Kept cmd out of the path entirely rather than escaping &/%, which is fragile (percent-encoded values such as redirect_uri=http%3A%2F%2F... can also trip cmd variable expansion).

Testing

  • New tests/open-browser.test.ts: asserts the Windows launcher is not cmd, the full URL (including everything after the first &) is passed as one argument, and macOS/Linux behavior is unchanged.
  • npx vitest run — 26 passed (3 new + 23 existing auth-provider tests).
  • npx tsc --noEmit — clean.

Please verify before merge

I validated via unit test + typecheck on macOS. Needs a manual smoke test on a real Windows machine: run the plugin sign-in and confirm the browser opens the full ?response_type=code&client_id=... URL and OAuth completes.

openBrowser() launched the authorize URL via 'cmd /c start'. cmd.exe treats the unquoted '&' between OAuth query params as a command separator, truncating the URL at '?response_type=code' and dropping client_id, so the server rejects sign-in with invalid_client. Windows only; macOS/Linux use execFile and are unaffected. Launch via explorer.exe (no shell) so the full URL reaches the browser intact.
@eshwar-sundar-glean

Copy link
Copy Markdown
Collaborator

this solution does not work on windows, tested it, it opens file explorer

explorer.exe is the Windows shell, not a reliable URL launcher: it only
sometimes forwards an http(s) argument to the default browser and
otherwise opens a File Explorer window, so OAuth sign-in never reached
the browser.

Route back through `cmd /c start` (which resolves the http protocol to
the default browser) but escape every `&` as `^&` and pass args verbatim
so Node doesn't re-quote and neutralize the `^`. cmd then un-escapes
`^&` to a literal `&`, so it no longer treats `&` as a command separator
and truncates the authorize URL at the first param -- which had dropped
client_id and triggered invalid_client.

Update the openBrowser Windows unit test accordingly.
@eshwar-sundar-glean

Copy link
Copy Markdown
Collaborator

Pushed a fix and tested it on windows

@eshwar-sundar-glean
eshwar-sundar-glean merged commit eca1a25 into main Jul 15, 2026
2 checks passed
@eshwar-sundar-glean
eshwar-sundar-glean deleted the fix/win32-oauth-authorize-url-truncation branch July 15, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants