From d132bf09338b60dc5ba9f5cac8659dd834779a43 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Sun, 18 Oct 2026 12:24:09 -0400 Subject: [PATCH 1/2] 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); }); From 4a6e54586a902a4c80615d3dae5e30b39ccbea17 Mon Sep 17 00:00:00 2001 From: Jared Wray Date: Mon, 19 Oct 2026 21:44:41 +0000 Subject: [PATCH 2/2] utils - fix: prevent excess backtracking in time parser --- packages/utils/src/shorthand-time.ts | 8 +++++--- packages/utils/test/shorthand-time.test.ts | 17 ++++++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/utils/src/shorthand-time.ts b/packages/utils/src/shorthand-time.ts index c5e8551d..30964d6d 100644 --- a/packages/utils/src/shorthand-time.ts +++ b/packages/utils/src/shorthand-time.ts @@ -28,9 +28,11 @@ 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. - // 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); + // Require at least one digit and at most one decimal point. + // Keep the decimal part in one group to prevent excess backtracking. + 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 8b26beae..10d38fb9 100644 --- a/packages/utils/test/shorthand-time.test.ts +++ b/packages/utils/test/shorthand-time.test.ts @@ -34,16 +34,19 @@ 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"]) { + test.each(["1.2.3s", "1..5s", "..s", ".s", ".m"])( + 'reject a malformed number in "%s"', + (value) => { 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.each([ + [".5s", 500], + ["5.s", 5000], + ])('convert "%s" to milliseconds', (value, expected) => { + expect(shorthandToMilliseconds(value)).toBe(expected); }); test("send in string with number", () => { expect(shorthandToMilliseconds("1000")).toBe(1000);