Repository navigation
utils - fix: reject malformed numbers in shorthandToMilliseconds - #1744
Conversation
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
left a comment
There was a problem hiding this comment.
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.eachfor the malformed inputs. Then a failure shows which input failed. - Add the four test-audit answers from
AGENTS.mdto the verification list in the PR body.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1744 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 29 29
Lines 3620 3620
Branches 824 812 -12
=========================================
Hits 3620 3620 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Commit 4a6e545 applies the requested regex change. The decimal part stays in one optional group to prevent polynomial backtracking. Both test groups now use
|
|
@rudycelekli - this is going to get merged today and I did the fix I asked for. Should be released in the next couple weeks. Thanks! |
Please check if the PR fulfills these requirements
What kind of change does this PR introduce? Bug fix (
@cacheable/utils)shorthandToMillisecondsnow rejects malformed numbers with valid time units. The old regex accepts multiple decimal points or returnsNaNfor a number without digits."1.2.3s"1200"1..5s"1000"..s",".s",".m"NaN".5s","5.s"500,5000500,5000The decimal part stays in one optional group to prevent polynomial backtracking:
Verification
pnpm buildpassed.node scripts/test-build.mjspassed for all packages.pnpm testpassed with 1,390 tests and coverage. Redis and the HTTP service supplied the test services.@cacheable/utilspassed. The changedshorthand-time.tsfile has 100% statement, branch, function, and line coverage.mainat3a37f54withexpected [Function] to throw an error.[0 1 . s m h r d space].x. The previous PR regex took 15,088 ms; the new regex took 0.509 ms..5sand5.s. A stricter regex can reject these valid values. Existing decimal tests use digits on both sides of the dot. These cases call the exported function without a test hook.send in string with malformed number and valid unit throwsbecamereject a malformed number in "%s". The table retains all five inputs and identifies each case in a failure.send in string with decimal edge casesbecameconvert "%s" to milliseconds. The table retains both original assertions and identifies each case in a failure.The original contributor used Claude Code. Codex prepared the follow-up change and ran the checks above.