mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 04:16:13 +00:00
fix(cli): reject malformed timeout options
This commit is contained in:
@@ -186,7 +186,11 @@ export function registerCronEditCommand(cron: Command) {
|
||||
patch.sessionTarget = sessionTarget;
|
||||
}
|
||||
if (typeof opts.wake === "string") {
|
||||
patch.wakeMode = opts.wake;
|
||||
const wakeMode = opts.wake.trim();
|
||||
if (wakeMode !== "now" && wakeMode !== "next-heartbeat") {
|
||||
throw new Error("--wake must be now or next-heartbeat");
|
||||
}
|
||||
patch.wakeMode = wakeMode;
|
||||
}
|
||||
if (opts.agent && opts.clearAgent) {
|
||||
throw new Error("Use --agent or --clear-agent, not both");
|
||||
@@ -229,10 +233,20 @@ export function registerCronEditCommand(cron: Command) {
|
||||
const model = normalizeOptionalString(opts.model);
|
||||
const thinking = normalizeOptionalString(opts.thinking);
|
||||
const toolsAllow = parseCronToolsAllow(opts.tools);
|
||||
const timeoutSeconds = opts.timeoutSeconds
|
||||
? Number.parseInt(String(opts.timeoutSeconds), 10)
|
||||
: undefined;
|
||||
const hasTimeoutSeconds = Boolean(timeoutSeconds && Number.isFinite(timeoutSeconds));
|
||||
const rawTimeoutSeconds =
|
||||
opts.timeoutSeconds === undefined ? undefined : String(opts.timeoutSeconds).trim();
|
||||
if (rawTimeoutSeconds !== undefined && !/^\d+$/u.test(rawTimeoutSeconds)) {
|
||||
throw new Error("Invalid --timeout-seconds (must be a positive integer).");
|
||||
}
|
||||
const timeoutSeconds =
|
||||
rawTimeoutSeconds === undefined ? undefined : Number(rawTimeoutSeconds);
|
||||
const hasTimeoutSeconds =
|
||||
typeof timeoutSeconds === "number" &&
|
||||
Number.isSafeInteger(timeoutSeconds) &&
|
||||
timeoutSeconds > 0;
|
||||
if (rawTimeoutSeconds !== undefined && !hasTimeoutSeconds) {
|
||||
throw new Error("Invalid --timeout-seconds (must be a positive integer).");
|
||||
}
|
||||
const hasDeliveryModeFlag = opts.announce || typeof opts.deliver === "boolean";
|
||||
const threadId = parseCronThreadIdOption(opts.threadId);
|
||||
const hasDeliveryThreadId = typeof threadId === "number";
|
||||
|
||||
@@ -10,6 +10,9 @@ describe("parseTimeoutMs", () => {
|
||||
expect(parseTimeoutMs(undefined)).toBeUndefined();
|
||||
expect(parseTimeoutMs("")).toBeUndefined();
|
||||
expect(parseTimeoutMs("nope")).toBeUndefined();
|
||||
expect(parseTimeoutMs("10abc")).toBeUndefined();
|
||||
expect(parseTimeoutMs("1.5")).toBeUndefined();
|
||||
expect(parseTimeoutMs("0")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,4 +43,12 @@ describe("parseTimeoutMsWithFallback", () => {
|
||||
expect(() => parseTimeoutMsWithFallback("0", 3000)).toThrow('Received: "0"');
|
||||
expect(() => parseTimeoutMsWithFallback("-1", 3000)).toThrow('Received: "-1"');
|
||||
});
|
||||
|
||||
it("throws on malformed or unsafe parsed values", () => {
|
||||
expect(() => parseTimeoutMsWithFallback("10abc", 3000)).toThrow('Received: "10abc"');
|
||||
expect(() => parseTimeoutMsWithFallback("1.5", 3000)).toThrow('Received: "1.5"');
|
||||
expect(() => parseTimeoutMsWithFallback(String(Number.MAX_SAFE_INTEGER + 1), 3000)).toThrow(
|
||||
"Received",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,9 +12,12 @@ export function parseTimeoutMs(raw: unknown): number | undefined {
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
value = Number.parseInt(trimmed, 10);
|
||||
if (!/^\d+$/u.test(trimmed)) {
|
||||
return undefined;
|
||||
}
|
||||
value = Number(trimmed);
|
||||
}
|
||||
return Number.isFinite(value) ? value : undefined;
|
||||
return Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
function invalidTimeout(value?: string): Error {
|
||||
@@ -53,8 +56,11 @@ export function parseTimeoutMsWithFallback(
|
||||
return fallbackMs;
|
||||
}
|
||||
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
if (!/^\d+$/u.test(value)) {
|
||||
throw invalidTimeout(value);
|
||||
}
|
||||
const parsed = Number(value);
|
||||
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
||||
throw invalidTimeout(value);
|
||||
}
|
||||
return parsed;
|
||||
|
||||
@@ -60,10 +60,11 @@ describe("createGlobalCommandRunner", () => {
|
||||
expect(parseTimeoutMsOrExit("0")).toBeNull();
|
||||
expect(parseTimeoutMsOrExit("-1")).toBeNull();
|
||||
expect(parseTimeoutMsOrExit(" ")).toBeNull();
|
||||
expect(parseTimeoutMsOrExit(String(Number.MAX_SAFE_INTEGER))).toBeNull();
|
||||
|
||||
expect(error).toHaveBeenCalledTimes(5);
|
||||
expect(error).toHaveBeenCalledTimes(6);
|
||||
expect(error).toHaveBeenCalledWith("--timeout must be a positive integer (seconds)");
|
||||
expect(exit).toHaveBeenCalledTimes(5);
|
||||
expect(exit).toHaveBeenCalledTimes(6);
|
||||
expect(exit).toHaveBeenCalledWith(1);
|
||||
} finally {
|
||||
error.mockRestore();
|
||||
|
||||
@@ -52,6 +52,7 @@ export type UpdateWizardOptions = {
|
||||
};
|
||||
|
||||
const INVALID_TIMEOUT_ERROR = "--timeout must be a positive integer (seconds)";
|
||||
const MAX_SAFE_TIMEOUT_SECONDS = Math.floor(Number.MAX_SAFE_INTEGER / 1000);
|
||||
|
||||
export function parseTimeoutMsOrExit(timeout?: string): number | undefined | null {
|
||||
if (timeout === undefined) {
|
||||
@@ -59,7 +60,12 @@ export function parseTimeoutMsOrExit(timeout?: string): number | undefined | nul
|
||||
}
|
||||
const trimmed = timeout.trim();
|
||||
const seconds = Number(trimmed);
|
||||
if (!/^\d+$/u.test(trimmed) || !Number.isSafeInteger(seconds) || seconds <= 0) {
|
||||
if (
|
||||
!/^\d+$/u.test(trimmed) ||
|
||||
!Number.isSafeInteger(seconds) ||
|
||||
seconds <= 0 ||
|
||||
seconds > MAX_SAFE_TIMEOUT_SECONDS
|
||||
) {
|
||||
defaultRuntime.error(INVALID_TIMEOUT_ERROR);
|
||||
defaultRuntime.exit(1);
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user