fix(infra): share trusted Windows process argv lookup

This commit is contained in:
Vincent Koc
2026-06-21 12:31:23 +02:00
parent 3a53eb5d77
commit 15c880aeff
3 changed files with 35 additions and 29 deletions
+3 -26
View File
@@ -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);
}
/**
+20
View File
@@ -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");
+12 -3
View File
@@ -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) {