From 93771c43394971bb71a554743d600c8a7c9dee02 Mon Sep 17 00:00:00 2001 From: Hashim1999164 Date: Fri, 4 Sep 2026 23:09:21 +0500 Subject: [PATCH] Avoid ReDoS when checking numeric option values Replace the nested digit regex with an equivalent pattern that does not backtrack on long non matching input. Keeps negative and decimal number option values working. --- index.js | 8 +++++++- test.js | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 3f60f4c..d5ca1e6 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,12 @@ class ArgError extends Error { } } +// Same acceptance as /^-?\d*(\.(?=\d))?\d*$/ without nested optional +// digit runs that backtrack on long non matching input (ReDoS). +function looksLikeNumber(str) { + return /^-?\d*(?:\.\d+)?$/.test(str); +} + function arg( opts, { @@ -153,7 +159,7 @@ function arg( (argv[i + 1].length > 1 && argv[i + 1][0] === '-' && !( - argv[i + 1].match(/^-?\d*(\.(?=\d))?\d*$/) && + looksLikeNumber(argv[i + 1]) && (type === Number || // eslint-disable-next-line no-undef (typeof BigInt !== 'undefined' && type === BigInt)) diff --git a/test.js b/test.js index 49e638f..d076cc4 100644 --- a/test.js +++ b/test.js @@ -551,3 +551,24 @@ test('should error if negative numeric argument is passed to non-negative argume ) ).to.throw(arg.ArgError, 'option requires argument: --str'); }); + +test('should parse negative decimal numbers as option values', () => { + const argv = ['--num', '-1.5']; + + expect(arg({ '--num': Number }, { argv })).to.deep.equal({ + _: [], + '--num': -1.5 + }); +}); + +test('should reject long non numeric option values without hanging', () => { + const argv = ['--foo', `-${'0'.repeat(20000)}-`]; + const start = Date.now(); + + expect(() => arg({ '--foo': Number }, { argv })).to.throw( + arg.ArgError, + 'option requires argument: --foo' + ); + + expect(Date.now() - start).to.be.below(1000); +});