diff --git a/src/infra/gateway-lock.ts b/src/infra/gateway-lock.ts index eed65942a886..6454dbac0026 100644 --- a/src/infra/gateway-lock.ts +++ b/src/infra/gateway-lock.ts @@ -14,7 +14,8 @@ import { z } from "zod"; import { resolveConfigPath, resolveGatewayLockDir, resolveStateDir } from "../config/paths.js"; import { isPidAlive } from "../shared/pid-alive.js"; import { safeParseJsonWithSchema } from "../utils/zod-parse.js"; -import { isGatewayArgv, parseProcCmdline, parseWindowsCmdline } from "./gateway-process-argv.js"; +import { isGatewayArgv, parseProcCmdline } from "./gateway-process-argv.js"; +import { readWindowsProcessArgsSync } from "./windows-port-pids.js"; const DEFAULT_TIMEOUT_MS = 5000; const DEFAULT_POLL_INTERVAL_MS = 100; @@ -79,32 +80,8 @@ function readLinuxCmdline(pid: number): string[] | null { const CMDLINE_EXEC_TIMEOUT_MS = 1000; -/** - * Read the command line of a Windows process via `wmic`. - * Returns an argv-style array, or null when the lookup fails (process gone, - * `wmic` missing/deprecated, timeout, etc.). - */ function readWindowsCmdline(pid: number): string[] | null { - try { - // Omit `encoding` so execFileSync returns a Buffer — wmic emits UTF-16LE - // (with BOM) on most Windows 10/11 builds, which would be garbled as UTF-8. - const buf = execFileSync( - "wmic", - ["process", "where", `processid=${pid}`, "get", "CommandLine", "/value"], - { timeout: CMDLINE_EXEC_TIMEOUT_MS, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }, - ) as Buffer; - const raw = - buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe - ? buf.toString("utf16le") - : buf.toString("utf8"); - const match = raw.match(/CommandLine=(.+)/); - if (!match) { - return null; - } - return parseWindowsCmdline(match[1].trim()); - } catch { - return null; - } + return readWindowsProcessArgsSync(pid, CMDLINE_EXEC_TIMEOUT_MS); } /** diff --git a/src/infra/gateway-processes.test.ts b/src/infra/gateway-processes.test.ts index c7fae9acad67..827622d4de8b 100644 --- a/src/infra/gateway-processes.test.ts +++ b/src/infra/gateway-processes.test.ts @@ -140,6 +140,26 @@ describe("gateway-processes", () => { expect(parseCmdScriptCommandLineMock).toHaveBeenCalledWith("node.exe gateway run"); }); + it("decodes UTF-16 WMIC output when reading windows process args", () => { + setPlatform("win32"); + spawnSyncMock + .mockReturnValueOnce({ + error: new Error("powershell missing"), + status: null, + stdout: "", + }) + .mockReturnValueOnce({ + error: null, + status: 0, + stdout: Buffer.from("\uFEFFCommandLine=node.exe gateway run\r\n", "utf16le"), + }); + parseCmdScriptCommandLineMock.mockReturnValue(["node.exe", "gateway", "run"]); + + expect(readGatewayProcessArgsSync(77)).toEqual(["node.exe", "gateway", "run"]); + expect(spawnSyncMock.mock.calls[1]?.[0]).toBe(getWindowsWmicExePath()); + expect(parseCmdScriptCommandLineMock).toHaveBeenCalledWith("node.exe gateway run"); + }); + it("signals only verified gateway processes", () => { setPlatform("linux"); readFileSyncMock.mockReturnValue("node\0gateway\0"); diff --git a/src/infra/windows-port-pids.ts b/src/infra/windows-port-pids.ts index bb1d0614f848..4712c8f3ad6a 100644 --- a/src/infra/windows-port-pids.ts +++ b/src/infra/windows-port-pids.ts @@ -95,8 +95,17 @@ export function readWindowsListeningPidsResultSync( // Windows process-args reading (PowerShell → WMIC fallback) // --------------------------------------------------------------------------- -function extractWindowsCommandLine(raw: string): string | null { - const lines = normalizeStringEntries(raw.split(/\r?\n/)); +function decodeWindowsProcessOutput(output: Buffer | string): string { + if (!Buffer.isBuffer(output)) { + return output; + } + return output.length >= 2 && output[0] === 0xff && output[1] === 0xfe + ? output.toString("utf16le") + : output.toString("utf8"); +} + +function extractWindowsCommandLine(raw: Buffer | string): string | null { + const lines = normalizeStringEntries(decodeWindowsProcessOutput(raw).split(/\r?\n/)); for (const line of lines) { if (!normalizeLowercaseStringOrEmpty(line).startsWith("commandline=")) { continue; @@ -140,9 +149,9 @@ export function readWindowsProcessArgsResultSync( getWindowsWmicExePath(), ["process", "where", `ProcessId=${pid}`, "get", "CommandLine", "/value"], { - encoding: "utf8", timeout: timeoutMs, windowsHide: true, + stdio: ["ignore", "pipe", "ignore"], }, ); if (!wmic.error && wmic.status === 0) {