mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:50:43 +00:00
fix: Provider-supplied OAuth URLs inject Windows cmd.exe via openUrl (#64161)
* fix: harden Windows browser URL opening Use explorer.exe directly for OAuth/browser launch on Windows so provider-supplied URLs are never parsed through cmd.exe metacharacter rules. * fix: harden Windows browser URL opening --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -40,6 +40,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Windows/onboarding: open provider OAuth and sign-in URLs with `explorer.exe` instead of routing them through `cmd /c start`, so quoted provider URLs cannot break out into host command execution. (#64161) Thanks @coygeek and @vincentkoc.
|
||||
- OpenAI/Codex OAuth: stop rewriting the upstream authorize URL scopes so new Codex sign-ins do not fail with `invalid_scope` before returning an authorization code. (#64713) Thanks @fuller-stack-dev.
|
||||
- Audio transcription: disable pinned DNS only for OpenAI-compatible multipart requests, while still validating hostnames, so OpenAI, Groq, and Mistral transcription works again without weakening other request paths. (#64766) Thanks @GodsBoy.
|
||||
- macOS/Talk Mode: after granting microphone permission on first enable, continue starting Talk Mode instead of requiring a second toggle. (#62459) Thanks @ggarber.
|
||||
|
||||
@@ -44,7 +44,7 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("openUrl", () => {
|
||||
it("quotes URLs on win32 so '&' is not treated as cmd separator", async () => {
|
||||
it("passes OAuth URLs to explorer.exe on win32 without cmd parsing", async () => {
|
||||
vi.stubEnv("VITEST", "");
|
||||
vi.stubEnv("NODE_ENV", "");
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
@@ -59,23 +59,20 @@ describe("openUrl", () => {
|
||||
|
||||
expect(mocks.runCommandWithTimeout).toHaveBeenCalledTimes(1);
|
||||
const [argv, options] = mocks.runCommandWithTimeout.mock.calls[0] ?? [];
|
||||
expect(argv?.slice(0, 4)).toEqual(["cmd", "/c", "start", '""']);
|
||||
expect(argv?.at(-1)).toBe(`"${url}"`);
|
||||
expect(options).toMatchObject({
|
||||
timeoutMs: 5_000,
|
||||
windowsVerbatimArguments: true,
|
||||
});
|
||||
expect(argv).toEqual(["explorer.exe", url]);
|
||||
expect(options).toMatchObject({ timeoutMs: 5_000 });
|
||||
expect(options?.windowsVerbatimArguments).toBeUndefined();
|
||||
|
||||
platformSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveBrowserOpenCommand", () => {
|
||||
it("marks win32 commands as quoteUrl=true", async () => {
|
||||
it("uses explorer.exe on win32", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const resolved = await resolveBrowserOpenCommand();
|
||||
expect(resolved.argv).toEqual(["cmd", "/c", "start", ""]);
|
||||
expect(resolved.quoteUrl).toBe(true);
|
||||
expect(resolved.argv).toEqual(["explorer.exe"]);
|
||||
expect(resolved.command).toBe("explorer.exe");
|
||||
platformSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ export type BrowserOpenCommand = {
|
||||
argv: string[] | null;
|
||||
reason?: string;
|
||||
command?: string;
|
||||
quoteUrl?: boolean;
|
||||
};
|
||||
|
||||
export type BrowserOpenSupport = {
|
||||
@@ -36,9 +35,8 @@ export async function resolveBrowserOpenCommand(): Promise<BrowserOpenCommand> {
|
||||
|
||||
if (platform === "win32") {
|
||||
return {
|
||||
argv: ["cmd", "/c", "start", ""],
|
||||
command: "cmd",
|
||||
quoteUrl: true,
|
||||
argv: ["explorer.exe"],
|
||||
command: "explorer.exe",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,21 +84,10 @@ export async function openUrl(url: string): Promise<boolean> {
|
||||
if (!resolved.argv) {
|
||||
return false;
|
||||
}
|
||||
const quoteUrl = resolved.quoteUrl === true;
|
||||
const command = [...resolved.argv];
|
||||
if (quoteUrl) {
|
||||
if (command.at(-1) === "") {
|
||||
command[command.length - 1] = '""';
|
||||
}
|
||||
command.push(`"${url}"`);
|
||||
} else {
|
||||
command.push(url);
|
||||
}
|
||||
command.push(url);
|
||||
try {
|
||||
await runCommandWithTimeout(command, {
|
||||
timeoutMs: 5_000,
|
||||
windowsVerbatimArguments: quoteUrl,
|
||||
});
|
||||
await runCommandWithTimeout(command, { timeoutMs: 5_000 });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user