Merge pull request #1225 from morluto/fix/exec-stop-timeout-validation
fix(process): refuse malformed timeouts for stoppable commands
This commit is contained in:
commit
136d4aab18
|
|
@ -123,6 +123,22 @@ describe.skipIf(process.platform === "win32")(
|
|||
expect(await readFile(stopped, "utf8")).toBe("SIGINT");
|
||||
});
|
||||
|
||||
it.each([-1, Number.NaN, 1.5, Number.POSITIVE_INFINITY, 2 ** 53])(
|
||||
"refuses a malformed timeout (%s) in both modes before starting the command",
|
||||
async (timeout) => {
|
||||
// Starting this command would fail with ENOENT instead.
|
||||
await expect(
|
||||
execFileOutput("/nonexistent/rea-command", [], {
|
||||
timeout,
|
||||
stopSignal: "SIGINT",
|
||||
}),
|
||||
).rejects.toMatchObject({ code: "ERR_OUT_OF_RANGE" });
|
||||
await expect(
|
||||
execFileOutput("/nonexistent/rea-command", [], { timeout }),
|
||||
).rejects.toMatchObject({ code: "ERR_OUT_OF_RANGE" });
|
||||
},
|
||||
);
|
||||
|
||||
it("kills a timed-out process that ignores the stop signal", async () => {
|
||||
const script = [
|
||||
'for (const name of ["SIGINT", "SIGTERM"]) process.on(name, () => {});',
|
||||
|
|
|
|||
|
|
@ -53,6 +53,21 @@ const timeoutError = (
|
|||
return error;
|
||||
};
|
||||
|
||||
/**
|
||||
* The `timeout` contract for both modes, checked before spawning. A stoppable
|
||||
* command keeps its own deadline, so execFile never validates it. Node 26's
|
||||
* execFile also caps it at Number.MAX_SAFE_INTEGER; Node 22 and 24 accept
|
||||
* larger integers, which their timers then clamp to 1 ms.
|
||||
*/
|
||||
const invalidTimeout = (timeout: number): RangeError | undefined => {
|
||||
if (Number.isSafeInteger(timeout) && timeout >= 0) return undefined;
|
||||
const error = new RangeError(
|
||||
`The value of "timeout" is out of range. It must be an unsigned integer. Received ${String(timeout)}`,
|
||||
);
|
||||
Reflect.set(error, "code", "ERR_OUT_OF_RANGE");
|
||||
return error;
|
||||
};
|
||||
|
||||
/** Captured subprocess output read from an execFileOutput rejection. */
|
||||
export interface ExecFileOutputFailure {
|
||||
readonly stdout: string;
|
||||
|
|
@ -93,6 +108,11 @@ export const execFileOutput = (
|
|||
): Promise<{ readonly stdout: string; readonly stderr: string }> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const { stopSignal, signal, timeout, ...execOptions } = options;
|
||||
const invalid = timeout === undefined ? undefined : invalidTimeout(timeout);
|
||||
if (invalid !== undefined) {
|
||||
reject(invalid);
|
||||
return;
|
||||
}
|
||||
if (stopSignal !== undefined && signal?.aborted === true) {
|
||||
reject(abortError(signal));
|
||||
return;
|
||||
|
|
|
|||
Loading…
Reference in New Issue