From d132bf09338b60dc5ba9f5cac8659dd834779a43 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Sun, 18 Oct 2026 12:24:09 -0400 Subject: [PATCH] utils - fix: reject malformed numbers in shorthandToMilliseconds The shorthand regex used [\d.]+ for the numeric part, so malformed values were not rejected: "1.2.3s" silently parsed as 1.2s (1200ms) and "..s" or ".s" returned NaN instead of throwing the documented unsupported-format error. Require a well-formed number (digits with at most one decimal point). Co-Authored-By: Claude Sonnet 5.5 --- packages/utils/src/shorthand-time.ts | 5 +++-- packages/utils/test/shorthand-time.test.ts | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/shorthand-time.ts b/packages/utils/src/shorthand-time.ts index 94f5c54f..c5e8551d 100644 --- a/packages/utils/src/shorthand-time.ts +++ b/packages/utils/src/shorthand-time.ts @@ -28,8 +28,9 @@ export const shorthandToMilliseconds = ( // Check if the string is purely numeric if (Number.isNaN(Number(shorthand))) { - // Use a case-insensitive regex that supports decimals and 'ms' unit - const match = /^([\d.]+)\s*(ms|s|m|h|hr|d)$/i.exec(shorthand); + // Use a case-insensitive regex that supports decimals and 'ms' unit. + // The number must be well-formed (at most one decimal point and at least one digit). + const match = /^(\d+\.?\d*|\.\d+)\s*(ms|s|m|h|hr|d)$/i.exec(shorthand); if (!match) { throw new Error( diff --git a/packages/utils/test/shorthand-time.test.ts b/packages/utils/test/shorthand-time.test.ts index c043096a..8b26beae 100644 --- a/packages/utils/test/shorthand-time.test.ts +++ b/packages/utils/test/shorthand-time.test.ts @@ -34,6 +34,17 @@ describe("time parser", () => { "Unsupported time format: \"1z\". Use 'ms', 's', 'm', 'h', 'hr', or 'd'.", ); }); + test("send in string with malformed number and valid unit throws", () => { + for (const value of ["1.2.3s", "1..5s", "..s", ".s", ".m"]) { + expect(() => shorthandToMilliseconds(value)).toThrowError( + `Unsupported time format: "${value}"`, + ); + } + }); + test("send in string with decimal edge cases", () => { + expect(shorthandToMilliseconds(".5s")).toBe(500); + expect(shorthandToMilliseconds("5.s")).toBe(5000); + }); test("send in string with number", () => { expect(shorthandToMilliseconds("1000")).toBe(1000); });