Skip to content

utils - fix: reject malformed numbers in shorthandToMilliseconds - #1744

Open
rudycelekli wants to merge 1 commit into
jaredwray:mainfrom
rudycelekli:utils-fix-shorthand-malformed-number
Open

rudycelekli wants to merge 1 commit into
jaredwray:mainfrom
rudycelekli:utils-fix-shorthand-malformed-number

Conversation

@rudycelekli

Copy link
Copy Markdown

Please check if the PR fulfills these requirements

  • Followed the Contributing guidelines and Code of Conduct
  • Tests for the changes have been added (for bug fixes/features) with 100% code coverage.

What kind of change does this PR introduce? Bug fix (@cacheable/utils)

shorthandToMilliseconds matched the numeric part of a shorthand with [\d.]+, so malformed numbers slipped through instead of hitting the "Unsupported time format" error:

input before after
"1.2.3s" 1200 (silently parsed as 1.2s) throws
"1..5s" 1000 throws
"..s", ".s" NaN throws
".5s", "5.s" 500, 5000 unchanged

A NaN TTL is especially bad since it flows into expiry math in the stores. The regex now requires digits with at most one decimal point: ^(\d+\.?\d*|\.\d+)\s*(ms|s|m|h|hr|d)$.

Verification (run from packages/utils with a standalone vitest 3 and biome, since pnpm install could not self-switch versions on my machine):

  • New test send in string with malformed number and valid unit throws fails on the old regex (expected [Function] to throw an error) and passes with the fix.
  • vitest run test/shorthand-time.test.ts test/ttl.test.ts: 38 passed.
  • biome check on the two touched files: clean.
  • Not run: the full workspace test suite, and coverage (so the 100% coverage box is left unchecked).

Written with Claude Code (AI-assisted); I reproduced the behavior and ran the tests above myself.

🤖 Generated with Claude Code

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 <noreply@anthropic.com>

@jaredwray jaredwray left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this fix. The new regex accepts and rejects the correct inputs. But it adds a performance problem.

Problem: polynomial backtracking (ReDoS) in the new regex

In \d+\.?\d*, the dot is optional. So \d+ and \d* can divide one run of digits in many ways. When the end of the string does not match, the engine tries each division. The time increases with the square of the input length. The old [\d.]+ did not have this problem.

Input main This PR Suggested regex
40,000 digits + "x" 0.5 ms 2,092 ms 0.5 ms

Cacheable.set, @cacheable/memory, and @cacheable/node-cache send string TTLs to this function without validation. A long TTL string from user input can block the event loop.

Requested change

Put the decimal part in one optional group at packages/utils/src/shorthand-time.ts:33:

const match = /^(\d+(?:\.\d*)?|\.\d+)\s*(ms|s|m|h|hr|d)$/i.exec(shorthand);

This regex gives the same captures as the regex in this PR. A comparison on all 597,870 strings of length 6 or less over [0 1 . s m h r d space] found 0 differences.

Optional

  • Use test.each for the malformed inputs. Then a failure shows which input failed.
  • Add the four test-audit answers from AGENTS.md to the verification list in the PR body.

@codecov

codecov Bot commented Oct 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (f885a2e) to head (d132bf0).

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #1744   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           29        29           
  Lines         3620      3620           
  Branches       810       811    +1     
=========================================
  Hits          3620      3620           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants