Skip to content

Commit e4d890d

Browse files
manuelMarkDenvertheofidry
authored andcommitted
fix(isURL): prevent URL validation bypass by improving protocol detection
1 parent 9079560 commit e4d890d

File tree

1 file changed

+26
-12
lines changed

1 file changed

+26
-12
lines changed

src/lib/isURL.js

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ max_allowed_length - if set, isURL will not allow URLs longer than the specified
3434
3535
*/
3636

37-
3837
const default_url_options = {
3938
protocols: ['http', 'https', 'ftp'],
4039
require_tld: true,
@@ -71,7 +70,10 @@ export default function isURL(url, options) {
7170
return false;
7271
}
7372

74-
if (!options.allow_query_components && (includes(url, '?') || includes(url, '&'))) {
73+
if (
74+
!options.allow_query_components &&
75+
(includes(url, '?') || includes(url, '&'))
76+
) {
7577
return false;
7678
}
7779

@@ -83,21 +85,33 @@ export default function isURL(url, options) {
8385
split = url.split('?');
8486
url = split.shift();
8587

86-
split = url.split('://');
87-
if (split.length > 1) {
88-
protocol = split.shift().toLowerCase();
89-
if (options.require_valid_protocol && options.protocols.indexOf(protocol) === -1) {
90-
return false;
88+
// Replaced the 'split("://")' logic with a regex to match the protocol.
89+
// This correctly identifies schemes like `javascript:` which don't use `//`.
90+
const protocol_match = url.match(/^([a-z][a-z0-9+\-.]*):/i);
91+
const hadExplicitProtocol = !!protocol_match;
92+
93+
if (protocol_match) {
94+
protocol = protocol_match[1].toLowerCase();
95+
if (
96+
options.require_valid_protocol &&
97+
options.protocols.indexOf(protocol) === -1
98+
) {
99+
return false; // The identified protocol is not in the allowed list.
91100
}
101+
url = url.substring(protocol_match[0].length); // Remove the protocol from the URL string.
92102
} else if (options.require_protocol) {
93-
return false;
94-
} else if (url.slice(0, 2) === '//') {
95-
if (!options.allow_protocol_relative_urls) {
103+
return false; // A protocol was required but not found.
104+
}
105+
106+
// Handle leading '//' only as protocol-relative when there was NO explicit protocol.
107+
// If there was an explicit protocol, '//' is the normal separator
108+
// and should be stripped unconditionally.
109+
if (url.slice(0, 2) === '//') {
110+
if (!hadExplicitProtocol && !options.allow_protocol_relative_urls) {
96111
return false;
97112
}
98-
split[0] = url.slice(2);
113+
url = url.slice(2); // Remove the '//' from the URL string.
99114
}
100-
url = split.join('://');
101115

102116
if (url === '') {
103117
return false;

0 commit comments

Comments
 (0)