feat(core): change the URL sanitization to only block javascript: URLs (#49659)

In modern browsers, the 'javascript:' URL scheme is the only scheme that
can execute JavaScript when passed in a navigation URL context (e.g.
`a.href` value). Validate URL shemes to only contain characters allowed
in the URL specification ([a-zA-Z-+.]), and that are not javascript
(case insensitive). This is not a breaking change. The URL sanitization
is loosen.

PR Close #49659
This commit is contained in:
Guillaume Weghsteen
2023-03-31 10:09:28 +00:00
committed by Dylan Hunn
parent f0978ad0ec
commit b35fa73968
2 changed files with 8 additions and 7 deletions
@@ -9,13 +9,14 @@
import {XSS_SECURITY_URL} from '../error_details_base_url';
/**
* A pattern that recognizes a commonly useful subset of URLs that are safe.
* A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
* contexts.
*
* This regular expression matches a subset of URLs that will not cause script
* execution if used in URL context within a HTML document. Specifically, this
* regular expression matches if (comment from here on and regex copied from
* Soy's EscapingConventions):
* (1) Either an allowed protocol (http, https, mailto or ftp).
* regular expression matches if:
* (1) Either a protocol that is not javascript:, and that has valid characters
* (alphanumeric or [+-.]).
* (2) or no protocol. A protocol must be followed by a colon. The below
* allows that by allowing colons only after one of the characters [/?#].
* A colon after a hash (#) must be in the fragment.
@@ -34,8 +35,7 @@ import {XSS_SECURITY_URL} from '../error_details_base_url';
*
* This regular expression was taken from the Closure sanitization library.
*/
const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|data|ftp|tel|file|sms):|[^&:/?#]*(?:[/?#]|$))/gi;
const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:\/?#]*(?:[\/?#]|$))/i;
export function _sanitizeUrl(url: string): string {
url = String(url);
if (url.match(SAFE_URL_PATTERN)) return url;
@@ -47,6 +47,7 @@ import {_sanitizeUrl} from '../../src/sanitization/url_sanitizer';
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated.
'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
'unknown-scheme:abc',
];
for (const url of validUrls) {
it(`valid ${url}`, () => expect(_sanitizeUrl(url)).toEqual(url));
@@ -57,7 +58,7 @@ import {_sanitizeUrl} from '../../src/sanitization/url_sanitizer';
const invalidUrls = [
'javascript:evil()',
'JavaScript:abc',
'evilNewProtocol:abc',
' javascript:abc',
' \n Java\n Script:abc',
'javascript:',
'&#106avascript:',