fix(security): use execFile for browser URL opening to prevent shell injection (#1022)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-17 18:28:24 -06:00 committed by GitHub
parent fd9565299c
commit 61858b914f
2 changed files with 11 additions and 11 deletions

View file

@ -2,7 +2,7 @@
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
import { getOAuthProviders } from "@gsd/pi-ai/oauth";
import { Container, type Focusable, getEditorKeybindings, Input, Spacer, Text, type TUI } from "@gsd/pi-tui";
import { exec } from "child_process";
import { execFile } from "child_process";
import { theme } from "../theme/theme.js";
import { DynamicBorder } from "./dynamic-border.js";
import { keyHint } from "./keybinding-hints.js";
@ -134,11 +134,11 @@ export class LoginDialogComponent extends Container implements Focusable {
// Try to open browser — on Windows, `start` needs an empty title arg
// so it treats the URL as a target, not a window title
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
if (process.platform === "win32") {
exec(`start "" "${url}"`);
execFile("cmd", ["/c", "start", "", url], () => {});
} else {
exec(`${openCmd} "${url}"`);
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
execFile(openCmd, [url], () => {});
}
this.tui.requestRender();

View file

@ -10,7 +10,7 @@
* All steps are skippable. All errors are recoverable. Never crashes boot.
*/
import { exec } from 'node:child_process'
import { execFile } from 'node:child_process'
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import type { AuthStorage } from '@gsd/pi-coding-agent'
@ -122,12 +122,12 @@ async function loadPico(): Promise<PicoModule> {
/** Open a URL in the system browser (best-effort, non-blocking) */
function openBrowser(url: string): void {
const cmd = process.platform === 'darwin' ? 'open' :
process.platform === 'win32' ? 'start' :
'xdg-open'
exec(`${cmd} "${url}"`, () => {
// Ignore errors — user can manually open the URL
})
if (process.platform === 'win32') {
execFile('cmd', ['/c', 'start', '', url], () => {})
} else {
const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open'
execFile(cmd, [url], () => {})
}
}
/** Check if an error is a clack cancel signal */