From 8a967ae6b8a92e52ab66307b0d8ae7eb2cf1c621 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Tue, 3 Feb 2026 15:28:39 -0500 Subject: [PATCH 1/2] fix: Potential ReDos vulnerability in url validator --- src/utils/validator.ts | 38 ++++++++++++++----------------- test/unit/utils/validator.spec.ts | 25 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/utils/validator.ts b/src/utils/validator.ts index fb738904e1..dadc36d44e 100644 --- a/src/utils/validator.ts +++ b/src/utils/validator.ts @@ -15,7 +15,8 @@ * limitations under the License. */ -import url = require('url'); + + /** * Validates that a value is a byte buffer. @@ -234,33 +235,28 @@ export function isURL(urlStr: any): boolean { return false; } try { - const uri = url.parse(urlStr); + const uri = new URL(urlStr); const scheme = uri.protocol; - const slashes = uri.slashes; - const hostname = uri.hostname; - const pathname = uri.pathname; - if ((scheme !== 'http:' && scheme !== 'https:') || !slashes) { + if (scheme !== 'http:' && scheme !== 'https:') { return false; } - // Validate hostname: Can contain letters, numbers, underscore and dashes separated by a dot. - // Each zone must not start with a hyphen or underscore. - if (!hostname || !/^[a-zA-Z0-9]+[\w-]*([.]?[a-zA-Z0-9]+[\w-]*)*$/.test(hostname)) { - return false; - } - // Allow for pathnames: (/chars+)*/? - // Where chars can be a combination of: a-z A-Z 0-9 - _ . ~ ! $ & ' ( ) * + , ; = : @ % - const pathnameRe = /^(\/[\w\-.~!$'()*+,;=:@%]+)*\/?$/; - // Validate pathname. - if (pathname && - pathname !== '/' && - !pathnameRe.test(pathname)) { - return false; + const hostname = uri.hostname; + // Validate hostname strictly to match previous behavior and prevent weak/invalid domains. + // Must be alphanumeric with optional dashes/underscores, separated by dots. + // Cannot start/end with dot or dash (mostly). + // This regex is safe (no nested quantifiers with overlap). + if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/.test(hostname)) { + // Check for IPv6 literals which are valid but behave differently. + // Node 'new URL' keeps brackets for IPv6: [::1] -> [::1] + // Check for IPv6 address (simple check for brackets) + if (!/^\[[a-fA-F0-9:.]+\]$/.test(hostname)) { + return false; + } } - // Allow any query string and hash as long as no invalid character is used. + return true; } catch (e) { return false; } - return true; } diff --git a/test/unit/utils/validator.spec.ts b/test/unit/utils/validator.spec.ts index f059839ca9..9c07648087 100644 --- a/test/unit/utils/validator.spec.ts +++ b/test/unit/utils/validator.spec.ts @@ -530,3 +530,28 @@ describe('isISODateString()', () => { expect(isISODateString(validISODateString)).to.be.true; }); }); + +describe('isURL() ReDoS and Long Inputs', () => { + it('should handle long valid URLs quickly', function () { + this.timeout(1000); + const longUrl = 'https://' + Array(50).fill('a').join('.') + '.com'; + expect(isURL(longUrl)).to.be.true; + }); + + it('should handle long invalid URLs quickly (ReDoS check)', function () { + this.timeout(1000); + const longInvalid = 'https://' + 'a'.repeat(22) + '!'; + expect(isURL(longInvalid)).to.be.false; + }); + + it('should handle very long domain with many segments', function () { + this.timeout(1000); + const manySegments = 'https://' + Array(100).fill('a').join('.') + '.com'; + expect(isURL(manySegments)).to.be.true; + }); + + it('should reject invalid dot usage caught by strict regex', function () { + expect(isURL('https://a.b')).to.be.true; + expect(isURL('https://a..b')).to.be.false; + }); +}); From 83f712e1f50e997168feea5de937e66b19e9ae94 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Tue, 3 Feb 2026 17:30:28 -0500 Subject: [PATCH 2/2] restore pathname validations --- src/utils/validator.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/utils/validator.ts b/src/utils/validator.ts index dadc36d44e..15b2f53e68 100644 --- a/src/utils/validator.ts +++ b/src/utils/validator.ts @@ -15,9 +15,6 @@ * limitations under the License. */ - - - /** * Validates that a value is a byte buffer. * @@ -253,6 +250,16 @@ export function isURL(urlStr: any): boolean { return false; } } + // Restore strict pathname validation: (/chars+)*/? + // Where chars can be a combination of: a-z A-Z 0-9 - _ . ~ ! $ & ' ( ) * + , ; = : @ % + const pathnameRe = /^(\/[\w\-.~!$'()*+,;=:@%]+)*\/?$/; + // Validate pathname. + const pathname = uri.pathname; + if (pathname && + pathname !== '/' && + !pathnameRe.test(pathname)) { + return false; + } return true; } catch (e) { return false;