CVE-2021-3795: Inefficient Regular Expression Complexity (CWE-1333)
value: semver-regex is vulnerable to Inefficient Regular Expression Complexity
CWE description: The product uses a regular expression with an inefficient, possibly exponential worst-case computational complexity that consumes excessive CPU cycles.
CVSS v3 base score: 7.5
Published: 2021-09-15T17:15Z
Repository: https://github.com/sindresorhus/semver-regex
Language: JavaScript
Files changed: index.js, readme.md, test.js
Commit date: 2021-09-11 17:08:38 +0700
Commit message: Fix ReDoS vulnerability

Diff: diff --git a/index.js b/index.js
index ee36738..3421c1e 100644
--- a/index.js
+++ b/index.js
@@ -1,3 +1,3 @@
 export default function semverRegex() {
-	return /(?<=^v?|\sv?)(?:(?:0|[1-9]\d*)\.){2}(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|[\da-z-]*[a-z-][\da-z-]*)(?:\.(?:0|[1-9]\d*|[\da-z-]*[a-z-][\da-z-]*))*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?\b/gi;
+	return /(?:(?<=^v?|\sv?)(?:(?:0|[1-9]\d{0,9})\.){2}(?:0|[1-9]\d{0,9})(?:-(?:0|[1-9]\d*?|[\da-z-]*?[a-z-][\da-z-]*?){0,100}(?:\.(?:0|[1-9]\d*?|[\da-z-]*?[a-z-][\da-z-]*?))*?){0,100}(?:\+[\da-z-]+?(?:\.[\da-z-]+?)*?){0,100}\b){1,200}/gi;
 }
diff --git a/readme.md b/readme.md
index c107479..8e9a4cf 100644
--- a/readme.md
+++ b/readme.md
@@ -24,11 +24,11 @@ semverRegex().exec('unicorn 1.0.0 rainbow')[0];
 
 'unicorn 1.0.0 and rainbow 2.1.3'.match(semverRegex());
 //=> ['1.0.0', '2.1.3']
 ```
 
-**Note:** For versions coming from user-input, it's up to you to truncate the string to a sensible length to prevent abuse. For example, 100 length.
+**Note:** For versions coming from user-input, you are recommended to truncate the string to a sensible length to prevent abuse. For example, 100 length.
 
 ## Related
 
 - [find-versions](https://github.com/sindresorhus/find-versions) - Find semver versions in a string
 - [latest-semver](https://github.com/sindresorhus/latest-semver) - Get the latest stable semver version from an array of versions
diff --git a/test.js b/test.js
index eff8866..af2e385 100644
--- a/test.js
+++ b/test.js
@@ -10,11 +10,12 @@ const fixtures = [
 	'2.7.2+asdf',
 	'1.2.3-a.b.c.10.d.5',
 	'2.7.2-foo+bar',
 	'1.2.3-alpha.10.beta',
 	'1.2.3-alpha.10.beta+build.unicorn.rainbow',
-	'foo 0.0.0 bar 0.0.0'
+	'foo 0.0.0 bar 0.0.0',
+	'99999.99999.99999'
 ];
 
 test('matches semver versions on test', t => {
 	for (const fixture of fixtures) {
 		t.regex(fixture, semverRegex());
@@ -108,6 +109,14 @@ test('invalid version does not cause catatrophic backtracking', t => {
 	const postfix = '.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'.repeat(99999);
 	t.regex(
 		`v1.1.3-0aa${postfix}$`,
 		semverRegex()
 	);
+
+	for (let index = 1; index <= 50000; index++) {
+		const start = Date.now();
+		const fixture = `0.0.0-0${'.-------'.repeat(index)}@`;
+		semverRegex().test(fixture);
+		const difference = Date.now() - start;
+		t.true(difference < 10, `Execution time: ${difference}`);
+	}
 });

Vulnerable code: 	return /(?<=^v?|\sv?)(?:(?:0|[1-9]\d*)\.){2}(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|[\da-z-]*[a-z-][\da-z-]*)(?:\.(?:0|[1-9]\d*|[\da-z-]*[a-z-][\da-z-]*))*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?\b/gi;
**Note:** For versions coming from user-input, it's up to you to truncate the string to a sensible length to prevent abuse. For example, 100 length.
	'foo 0.0.0 bar 0.0.0'
Fixed code: 	return /(?:(?<=^v?|\sv?)(?:(?:0|[1-9]\d{0,9})\.){2}(?:0|[1-9]\d{0,9})(?:-(?:0|[1-9]\d*?|[\da-z-]*?[a-z-][\da-z-]*?){0,100}(?:\.(?:0|[1-9]\d*?|[\da-z-]*?[a-z-][\da-z-]*?))*?){0,100}(?:\+[\da-z-]+?(?:\.[\da-z-]+?)*?){0,100}\b){1,200}/gi;
**Note:** For versions coming from user-input, you are recommended to truncate the string to a sensible length to prevent abuse. For example, 100 length.
	'foo 0.0.0 bar 0.0.0',
	'99999.99999.99999'

	for (let index = 1; index <= 50000; index++) {
		const start = Date.now();
		const fixture = `0.0.0-0${'.-------'.repeat(index)}@`;
		semverRegex().test(fixture);
		const difference = Date.now() - start;
		t.true(difference < 10, `Execution time: ${difference}`);
	}