diff --git a/src/commands/artifacts-command.ts b/src/commands/artifacts-command.ts index dfbd24a..d60c29c 100644 --- a/src/commands/artifacts-command.ts +++ b/src/commands/artifacts-command.ts @@ -30,6 +30,7 @@ import { parseCommanderProgram, } from "./commander-utils.js"; import { setStderrDevLogStorageRootDirectory } from "../runtime/stderr-dev-log.js"; +import { parseNonNegativeInt } from "./option-parsers.js"; interface WriteTarget { write(chunk: string): unknown; @@ -391,14 +392,6 @@ function parseHarnessKindOption(value: string): AgentHarnessKind { ); } -function parseNonNegativeInt(value: string): number { - const parsed = Number.parseInt(value, 10); - if (!Number.isFinite(parsed) || parsed < 0) { - throw new InvalidArgumentError("value must be a non-negative integer"); - } - return parsed; -} - function writeJson(stdout: WriteTarget, value: unknown): void { stdout.write(`${JSON.stringify(value, null, 2)}\n`); } diff --git a/src/commands/option-parsers.test.ts b/src/commands/option-parsers.test.ts index fec3f91..c660576 100644 --- a/src/commands/option-parsers.test.ts +++ b/src/commands/option-parsers.test.ts @@ -35,6 +35,12 @@ describe("parsePositiveInt", () => { it("throws for non-numeric strings", () => { expect(() => parsePositiveInt("abc")).toThrow("positive integer"); }); + + it("rejects partially parsed and unsafe integer inputs", () => { + for (const value of ["12px", "1.5", "1e3", "9007199254740992"]) { + expect(() => parsePositiveInt(value)).toThrow("positive integer"); + } + }); }); describe("parseNonNegativeInt", () => { @@ -45,6 +51,10 @@ describe("parseNonNegativeInt", () => { it("throws for negative", () => { expect(() => parseNonNegativeInt("-1")).toThrow("non-negative"); }); + + it("rejects partially parsed values", () => { + expect(() => parseNonNegativeInt("0items")).toThrow("non-negative"); + }); }); describe("parseNumber", () => { @@ -55,6 +65,12 @@ describe("parseNumber", () => { it("throws for non-numeric", () => { expect(() => parseNumber("xyz")).toThrow("Expected number"); }); + + it("rejects malformed numeric values", () => { + for (const value of ["3.14ms", "1.2.3", "Infinity"]) { + expect(() => parseNumber(value)).toThrow("Expected number"); + } + }); }); describe("parseOperatingMode", () => { diff --git a/src/commands/option-parsers.ts b/src/commands/option-parsers.ts index 120f78e..041a97b 100644 --- a/src/commands/option-parsers.ts +++ b/src/commands/option-parsers.ts @@ -14,9 +14,28 @@ import { MIN_ANTHROPIC_THINKING_BUDGET_TOKENS } from "../bootstrap/config/defaul export class InvalidArgumentError extends Error {} +const DECIMAL_INTEGER_PATTERN = /^[+-]?\d+$/u; +const DECIMAL_NUMBER_PATTERN = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/iu; + +function parseDecimalInteger(value: string): number { + const normalized = value.trim(); + if (!DECIMAL_INTEGER_PATTERN.test(normalized)) { + return Number.NaN; + } + return Number(normalized); +} + +function parseFiniteNumber(value: string): number { + const normalized = value.trim(); + if (!DECIMAL_NUMBER_PATTERN.test(normalized)) { + return Number.NaN; + } + return Number(normalized); +} + export function parsePositiveInt(value: string): number { - const parsed = Number.parseInt(value, 10); - if (!Number.isInteger(parsed) || parsed <= 0) { + const parsed = parseDecimalInteger(value); + if (!Number.isSafeInteger(parsed) || parsed <= 0) { throw new Error(`Expected positive integer, got: ${value}`); } return parsed; @@ -32,8 +51,8 @@ export function parseOperatingMode(value: string): AgentOperatingMode { } export function parseNonNegativeInt(value: string): number { - const parsed = Number.parseInt(value, 10); - if (!Number.isInteger(parsed) || parsed < 0) { + const parsed = parseDecimalInteger(value); + if (!Number.isSafeInteger(parsed) || parsed < 0) { throw new Error(`Expected non-negative integer, got: ${value}`); } return parsed; @@ -107,7 +126,7 @@ export function parseToolSearchIndexProfile( } export function parseNumber(value: string): number { - const parsed = Number.parseFloat(value); + const parsed = parseFiniteNumber(value); if (!Number.isFinite(parsed)) { throw new Error(`Expected number, got: ${value}`); }