-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
fix(isURL): improve protocol detection. Resolves CVE-2025-56200 #2608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+178
−9
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
9079560
test: Add a test to capture the GHSA-9965-vmph-33xx vulnerability
theofidry e4d890d
fix(isURL): prevent URL validation bypass by improving protocol detec…
manuelMarkDenver 3706996
fix(isURL): Correct the patch and apply feedback
theofidry d7a67eb
docs: Add mention in the documentation.
theofidry 6d90c60
test(isURL): Add more tests
scottgigante-hubflow a37cc4d
test(isURL): Add more tests
theofidry 49d2408
test(isURL): Add coverage for missing else path (#3)
henri-extravagant 1f52cc9
Update test/validators.test.js
WikiRik 17aa26a
Update .github/workflows/ci.yml
WikiRik 376c84f
Update .github/workflows/ci.yml
WikiRik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -167,7 +167,7 @@ Validator | Description | |
| **isStrongPassword(str [, options])** | check if the string can be considered a strong password or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.<br/>Default options: <br/>`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }` | ||
| **isTime(str [, options])** | check if the string is a valid time e.g. [`23:01:59`, new Date().toLocaleTimeString()].<br/><br/> `options` is an object which can contain the keys `hourFormat` or `mode`.<br/><br/>`hourFormat` is a key and defaults to `'hour24'`.<br/><br/>`mode` is a key and defaults to `'default'`. <br/><br/>`hourFormat` can contain the values `'hour12'` or `'hour24'`, `'hour24'` will validate hours in 24 format and `'hour12'` will validate hours in 12 format. <br/><br/>`mode` can contain the values `'default', 'withSeconds', withOptionalSeconds`, `'default'` will validate `HH:MM` format, `'withSeconds'` will validate the `HH:MM:SS` format, `'withOptionalSeconds'` will validate `'HH:MM'` and `'HH:MM:SS'` formats. | ||
| **isTaxID(str, locale)** | check if the string is a valid Tax Identification Number. Default locale is `en-US`.<br/><br/>More info about exact TIN support can be found in `src/lib/isTaxID.js`.<br/><br/>Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-AR', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV', 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE', 'uk-UA']`. | ||
| **isURL(str [, options])** | check if the string is a URL.<br/><br/>`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.<br/><br/>`protocols` - valid protocols can be modified with this option.<br/>`require_tld` - If set to false isURL will not check if the URL's host includes a top-level domain.<br/>`require_protocol` - if set to true isURL will return false if protocol is not present in the URL.<br/>`require_host` - if set to false isURL will not check if host is present in the URL.<br/>`require_port` - if set to true isURL will check if port is present in the URL.<br/>`require_valid_protocol` - isURL will check if the URL's protocol is present in the protocols option.<br/>`allow_underscores` - if set to true, the validator will allow underscores in the URL.<br/>`host_whitelist` - if set to an array of strings or regexp, and the domain matches none of the strings defined in it, the validation fails.<br/>`host_blacklist` - if set to an array of strings or regexp, and the domain matches any of the strings defined in it, the validation fails.<br/>`allow_trailing_dot` - if set to true, the validator will allow the domain to end with a `.` character.<br/>`allow_protocol_relative_urls` - if set to true protocol relative URLs will be allowed.<br/>`allow_fragments` - if set to false isURL will return false if fragments are present.<br/>`allow_query_components` - if set to false isURL will return false if query components are present.<br/>`disallow_auth` - if set to true, the validator will fail if the URL contains an authentication component, e.g. `http://username:[email protected]`.<br/>`validate_length` - if set to false isURL will skip string length validation. `max_allowed_length` will be ignored if this is set as `false`.<br/>`max_allowed_length` - if set, isURL will not allow URLs longer than the specified value (default is 2084 that IE maximum URL length).<br/> | ||
| **isURL(str [, options])** | check if the string is a URL.<br/><br/>`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.<br/><br/>`protocols` - valid protocols can be modified with this option.<br/>`require_tld` - If set to false isURL will not check if the URL's host includes a top-level domain.<br/>`require_protocol` - **RECOMMENDED** if set to true isURL will return false if protocol is not present in the URL. Without this setting, some malicious URLs cannot be distinguishable from a valid URL with authentication information.<br/>`require_host` - if set to false isURL will not check if host is present in the URL.<br/>`require_port` - if set to true isURL will check if port is present in the URL.<br/>`require_valid_protocol` - isURL will check if the URL's protocol is present in the protocols option.<br/>`allow_underscores` - if set to true, the validator will allow underscores in the URL.<br/>`host_whitelist` - if set to an array of strings or regexp, and the domain matches none of the strings defined in it, the validation fails.<br/>`host_blacklist` - if set to an array of strings or regexp, and the domain matches any of the strings defined in it, the validation fails.<br/>`allow_trailing_dot` - if set to true, the validator will allow the domain to end with a `.` character.<br/>`allow_protocol_relative_urls` - if set to true protocol relative URLs will be allowed.<br/>`allow_fragments` - if set to false isURL will return false if fragments are present.<br/>`allow_query_components` - if set to false isURL will return false if query components are present.<br/>`disallow_auth` - if set to true, the validator will fail if the URL contains an authentication component, e.g. `http://username:[email protected]`.<br/>`validate_length` - if set to false isURL will skip string length validation. `max_allowed_length` will be ignored if this is set as `false`.<br/>`max_allowed_length` - if set, isURL will not allow URLs longer than the specified value (default is 2084 that IE maximum URL length).<br/> | ||
| **isULID(str)** | check if the string is a [ULID](https://github.com/ulid/spec). | ||
| **isUUID(str [, version])** | check if the string is an RFC9562 UUID.<br/>`version` is one of `'1'`-`'8'`, `'nil'`, `'max'`, `'all'` or `'loose'`. The `'loose'` option checks if the string is a UUID-like string with hexadecimal values, ignoring RFC9565. | ||
| **isVariableWidth(str)** | check if the string contains a mixture of full and half-width chars. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -424,6 +424,12 @@ describe('Validators', () => { | |
| 'http://[2010:836B:4179::836B:4179]', | ||
| 'http://example.com/example.json#/foo/bar', | ||
| 'http://1337.com', | ||
| // TODO: those probably should not be marked as valid URLs; CVE-2025-56200 | ||
| /* eslint-disable no-script-url */ | ||
| 'javascript:%61%6c%65%72%74%28%31%[email protected]', | ||
| 'http://[email protected]/', | ||
| 'javascript:alert(1)@example.com', | ||
| /* eslint-enable no-script-url */ | ||
| ], | ||
| invalid: [ | ||
| 'http://localhost:3000/', | ||
|
|
@@ -466,6 +472,18 @@ describe('Validators', () => { | |
| '////foobar.com', | ||
| 'http:////foobar.com', | ||
| 'https://example.com/foo/<script>alert(\'XSS\')</script>/', | ||
| // the following tests are because of CVE-2025-56200 | ||
| /* eslint-disable no-script-url */ | ||
| "javascript:alert(1);a=';@example.com/alert(1)'", | ||
| 'JaVaScRiPt:alert(1)@example.com', | ||
| 'javascript:/* comment */alert(1)@example.com', | ||
| 'javascript:var a=1; alert(a);@example.com', | ||
| 'javascript:alert(1)@[email protected]', | ||
| 'javascript:alert(1)@example.com?q=safe', | ||
| 'data:text/html,<script>alert(1)</script>@example.com', | ||
| 'vbscript:msgbox("XSS")@example.com', | ||
| '//evil-site.com/[email protected]', | ||
| /* eslint-enable no-script-url */ | ||
| ], | ||
| }); | ||
| }); | ||
|
|
@@ -478,9 +496,11 @@ describe('Validators', () => { | |
| }], | ||
| valid: [ | ||
| 'rtmp://foobar.com', | ||
| 'rtmp:foobar.com', | ||
| ], | ||
| invalid: [ | ||
| 'http://foobar.com', | ||
| 'tel:+15551234567', | ||
| ], | ||
| }); | ||
| }); | ||
|
|
@@ -533,6 +553,9 @@ describe('Validators', () => { | |
| 'rtmp://foobar.com', | ||
| 'http://foobar.com', | ||
| 'test://foobar.com', | ||
| // Dangerous! This allows to mark malicious URLs as a valid URL (CVE-2025-56200) | ||
| // eslint-disable-next-line no-script-url | ||
| 'javascript:alert(1);@example.com', | ||
| ], | ||
| invalid: [ | ||
| 'mailto:[email protected]', | ||
|
|
@@ -704,6 +727,61 @@ describe('Validators', () => { | |
| }); | ||
| }); | ||
|
|
||
| it('should validate authentication strings if a protocol is not required', () => { | ||
| test({ | ||
| validator: 'isURL', | ||
| args: [{ | ||
| require_protocol: false, | ||
| }], | ||
| valid: [ | ||
| 'user:[email protected]/', | ||
| ], | ||
| invalid: [ | ||
| 'user:pw,@foobar.com/', | ||
| ], | ||
| }); | ||
| }); | ||
|
|
||
| it('should reject authentication strings if a protocol is required', () => { | ||
| test({ | ||
| validator: 'isURL', | ||
| args: [{ | ||
| require_protocol: true, | ||
| }], | ||
| valid: [ | ||
| 'http://user:[email protected]/', | ||
| 'https://user:[email protected]', | ||
| 'ftp://admin:[email protected]/', | ||
| ], | ||
| invalid: [ | ||
| 'user:[email protected]/', | ||
| 'user:[email protected]', | ||
| 'admin:[email protected]/', | ||
| ], | ||
| }); | ||
| }); | ||
|
|
||
| it('should reject invalid protocols when require_valid_protocol is enabled', () => { | ||
| test({ | ||
| validator: 'isURL', | ||
| args: [{ | ||
| require_valid_protocol: true, | ||
| protocols: ['http', 'https', 'ftp'], | ||
| }], | ||
| valid: [ | ||
| 'http://example.com', | ||
| 'https://example.com', | ||
| 'ftp://example.com', | ||
| ], | ||
| invalid: [ | ||
| // eslint-disable-next-line no-script-url | ||
| 'javascript:alert(1);@example.com', | ||
| 'data:text/html,<script>alert(1)</script>@example.com', | ||
| 'file:///etc/[email protected]', | ||
| ], | ||
| }); | ||
| }); | ||
|
|
||
| it('should let users specify a host whitelist', () => { | ||
| test({ | ||
| validator: 'isURL', | ||
|
|
@@ -782,6 +860,24 @@ describe('Validators', () => { | |
| }); | ||
| }); | ||
|
|
||
| it('GHSA-9965-vmph-33xx vulnerability - protocol delimiter parsing difference', () => { | ||
| const DOMAIN_WHITELIST = ['example.com']; | ||
|
|
||
| test({ | ||
| validator: 'isURL', | ||
| args: [{ | ||
| protocols: ['https'], | ||
| host_whitelist: DOMAIN_WHITELIST, | ||
| require_host: false, | ||
| }], | ||
| valid: [], | ||
| invalid: [ | ||
| // eslint-disable-next-line no-script-url | ||
| "javascript:alert(1);a=';@example.com/alert(1)", | ||
| ], | ||
| }); | ||
| }); | ||
|
|
||
| it('should allow rejecting urls containing authentication information', () => { | ||
| test({ | ||
| validator: 'isURL', | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.