Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { stripInvisibleUnicode } from 'roosterjs-content-model-dom';

/**
* @internal Check if there is XSS attack in the link
* @param link The link to be checked
* @returns The safe link with invisible Unicode characters stripped, or empty string if there is XSS attack
* @remarks This function strips invisible Unicode characters (zero-width chars, Unicode Tags, etc.)
* and checks for patterns like s\nc\nr\ni\np\nt: to prevent XSS attacks. This may block some valid links,
* @returns The safe link, or empty string if there is XSS attack
* @remarks This function checks for patterns like s\nc\nr\ni\np\nt: to prevent XSS attacks. This may block some valid links,
* but it is necessary for security reasons. We treat the word "script" as safe if there are "/" before it.
*/
export function checkXss(link: string): string {
// Defense-in-depth: strip invisible Unicode even if already handled elsewhere
const sanitized = stripInvisibleUnicode(link);
return sanitized.match(/^[^\/]*s\n*c\n*r\n*i\n*p\n*t\n*:/i) ? '' : sanitized;
return link.match(/^[^\/]*s\n*c\n*r\n*i\n*p\n*t\n*:/i) ? '' : link;
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,36 +30,4 @@ describe('checkXss', () => {
const link = 'https://example.com/script:.js';
expect(checkXss(link)).toBe(link);
});

it('should strip invisible Unicode from link', () => {
const link = 'https://www\u200B.example\u200C.com';
expect(checkXss(link)).toBe('https://www.example.com');
});

it('should strip invisible Unicode from mailto link', () => {
const link = 'mailto:\u200Buser@example.com';
expect(checkXss(link)).toBe('mailto:user@example.com');
});

it('should detect XSS hidden behind invisible Unicode in script:', () => {
// script: with zero-width spaces between characters should still be caught
const link = 's\u200Bc\u200Cr\u200Di\u200Ep\u200Ft:alert(1)';
expect(checkXss(link)).toBe('');
});

it('should strip Unicode Tags (supplementary plane) from link', () => {
// U+E0061 = \uDB40\uDC61 (Tag Latin Small Letter A)
const link = 'mailto:\uDB40\uDC61user@example.com';
expect(checkXss(link)).toBe('mailto:user@example.com');
});

it('should strip bidirectional marks from link', () => {
const link = 'mailto:\u202Auser\u202E@example.com';
expect(checkXss(link)).toBe('mailto:user@example.com');
});

it('should strip invisible Unicode from mailto subject and body', () => {
const link = 'mailto:user@example.com?subject=Hello\u200BWorld&body=Test\u200CContent';
expect(checkXss(link)).toBe('mailto:user@example.com?subject=HelloWorld&body=TestContent');
});
});
3 changes: 0 additions & 3 deletions packages/roosterjs-content-model-core/lib/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
transformColor,
createDomToModelContextWithConfig,
domToContentModel,
sanitizeInvisibleUnicode,
} from 'roosterjs-content-model-dom';
import type {
ContentModelDocument,
Expand Down Expand Up @@ -53,8 +52,6 @@ export class Editor implements IEditor {
const initialModel =
options.initialModel ?? createEmptyModel(this.core.format.defaultFormat);

sanitizeInvisibleUnicode(initialModel);

this.core.api.setContentModel(
this.core,
initialModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ describe('Editor', () => {
updateKnownColorSpy = jasmine.createSpy('updateKnownColor');
createEditorCoreSpy = spyOn(createEditorCore, 'createEditorCore').and.callThrough();
setContentModelSpy = jasmine.createSpy('setContentModel');
createEmptyModelSpy = spyOn(createEmptyModel, 'createEmptyModel').and.callThrough();
createEmptyModelSpy = spyOn(createEmptyModel, 'createEmptyModel');
});

it('ctor and dispose, no options', () => {
const div = document.createElement('div');

createEmptyModelSpy.and.callThrough();

const editor = new Editor(div);

expect(createEditorCoreSpy).toHaveBeenCalledWith(div, {});
Expand Down Expand Up @@ -65,7 +67,7 @@ describe('Editor', () => {
} as any;
const setContentModelSpy = jasmine.createSpy('setContentModel');
const disposeErrorHandlerSpy = jasmine.createSpy('disposeErrorHandler');
const mockedInitialModel = { blocks: [] } as any;
const mockedInitialModel = 'INITMODEL' as any;
const options: EditorOptions = {
plugins: [mockedPlugin1, mockedPlugin2],
disposeErrorHandler: disposeErrorHandlerSpy,
Expand All @@ -76,6 +78,8 @@ describe('Editor', () => {
},
};

createEmptyModelSpy.and.callThrough();

const editor = new Editor(div, options);

expect(createEditorCoreSpy).toHaveBeenCalledWith(div, options);
Expand Down

This file was deleted.

2 changes: 0 additions & 2 deletions packages/roosterjs-content-model-dom/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export { addTextSegment } from './modelApi/common/addTextSegment';
export { normalizeParagraph } from './modelApi/common/normalizeParagraph';

export { normalizeContentModel } from './modelApi/common/normalizeContentModel';
export { sanitizeInvisibleUnicode } from './modelApi/common/sanitizeInvisibleUnicode';
export { isGeneralSegment } from './modelApi/typeCheck/isGeneralSegment';
export { unwrapBlock } from './modelApi/common/unwrapBlock';
export { addSegment } from './modelApi/common/addSegment';
Expand Down Expand Up @@ -119,7 +118,6 @@ export { isCharacterValue, isModifierKey, isCursorMovingKey } from './domUtils/e
export { getNodePositionFromEvent } from './domUtils/event/getNodePositionFromEvent';
export { combineBorderValue, extractBorderValues } from './domUtils/style/borderValues';
export { isPunctuation, isSpace, normalizeText } from './domUtils/stringUtil';
export { stripInvisibleUnicode } from './domUtils/stripInvisibleUnicode';
export { parseTableCells } from './domUtils/table/parseTableCells';
export { readFile } from './domUtils/readFile';
export { retrieveDocumentMetadata } from './domUtils/retrieveDocumentMetadata';
Expand Down

This file was deleted.

Loading
Loading