Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions packages/core/src/sanitization/url_sanitizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ import {XSS_SECURITY_URL} from '../error_details_base_url';
* 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:
* (1) Either a protocol that is not javascript:, and that has valid characters
* (alphanumeric or [+-.]).
* (1) Either a protocol that is not javascript: or vbscript:, and that has
* valid characters (alphanumeric or [+-.]).
* For data: URIs, only non-executable subtypes are allowed:
* image/* (except image/svg+xml), video/*, audio/*, font/*,
* application/octet-stream, application/pdf, application/json,
* text/plain, text/markdown, and text/csv.
* Other data: subtypes (e.g. data:text/html, data:text/javascript)
* are blocked as they can lead to script execution.
* (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.
Expand All @@ -35,7 +41,9 @@ import {XSS_SECURITY_URL} from '../error_details_base_url';
*
* This regular expression was taken from the Closure sanitization library.
*/
const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:\/?#]*(?:[\/?#]|$))/i;

const SAFE_URL_PATTERN =
/^(?!javascript:)(?!vbscript:)(?!data:(?!image\/(?!svg\+xml(?=[;,]))|video\/|audio\/|font\/|application\/octet-stream(?=[;,])|application\/pdf(?=[;,])|application\/json(?=[;,])|text\/plain(?=[;,])|text\/markdown(?=[;,])|text\/csv(?=[;,])))(?:[a-z0-9+.-]+:|[^&:\/?#]*(?:[\/?#]|$))/i;
export function _sanitizeUrl(url: string): string {
url = String(url);
if (url.match(SAFE_URL_PATTERN)) return url;
Expand Down
37 changes: 36 additions & 1 deletion packages/core/test/sanitization/url_sanitizer_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ describe('URL sanitizer', () => {
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated.
'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
'data:application/octet-stream;base64,dGVzdA==',
'data:text/plain,hello',
'data:application/pdf;base64,abc',
'data:application/json,{"key":"value"}',
'data:font/woff2;base64,abc',
'data:text/csv,a%2Cb%2Cc',
'data:text/markdown,# Hello',
'DATA:IMAGE/PNG;base64,abc',
'data:TEXT/PLAIN,hello',
'data:text/plain;charset=utf-8,hello world',
'unknown-scheme:abc',
];
for (const url of validUrls) {
Expand All @@ -68,7 +79,31 @@ describe('URL sanitizer', () => {
'jav\u0000ascript:alert();',
];
for (const url of invalidUrls) {
it(`valid ${url}`, () => expect(_sanitizeUrl(url)).toMatch(/^unsafe:/));
it(`invalid ${url}`, () => expect(_sanitizeUrl(url)).toMatch(/^unsafe:/));
}
});

describe('vbscript URLs', () => {
const vbscriptUrls = ['vbscript:MsgBox("XSS")', 'VBScript:alert()', 'VBSCRIPT:MsgBox("XSS")'];
for (const url of vbscriptUrls) {
it(`blocks ${url}`, () => expect(_sanitizeUrl(url)).toMatch(/^unsafe:/));
}
});

describe('dangerous data: URLs', () => {
const dangerousDataUrls = [
'data:text/html,<script>alert(1)</script>',
'data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==',
'data:application/xhtml+xml,<script>alert(1)</script>',
'data:text/xml,<script>alert(1)</script>',
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==',
'DATA:text/html,<script>alert(1)</script>',
'data:,<script>alert(1)</script>',
'data:application/javascript,alert(1)',
'data:text/javascript,alert(1)',
];
for (const url of dangerousDataUrls) {
it(`blocks ${url}`, () => expect(_sanitizeUrl(url)).toMatch(/^unsafe:/));
}
});
});
Loading