From c73e7b1a01ade346dc3366dbab59df497195a328 Mon Sep 17 00:00:00 2001 From: r00gm Date: Fri, 13 Sep 2024 10:11:56 +0200 Subject: [PATCH 1/3] fix: sanitize clipboard data --- .../composables/__tests__/useClipboard.test.ts | 15 ++++++++++++++- .../editor-ui/src/composables/useClipboard.ts | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/composables/__tests__/useClipboard.test.ts b/packages/editor-ui/src/composables/__tests__/useClipboard.test.ts index 6c4b977a0b707..cf2637fb5a586 100644 --- a/packages/editor-ui/src/composables/__tests__/useClipboard.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useClipboard.test.ts @@ -1,4 +1,4 @@ -import { render } from '@testing-library/vue'; +import { render, within } from '@testing-library/vue'; import userEvent from '@testing-library/user-event'; import { defineComponent, h, ref } from 'vue'; import { useClipboard } from '@/composables/useClipboard'; @@ -8,9 +8,13 @@ const testValue = 'This is a test'; const TestComponent = defineComponent({ setup() { const pasted = ref(''); + const htmlContent = ref(); const clipboard = useClipboard({ onPaste(data) { pasted.value = data; + if (htmlContent.value) { + htmlContent.value.innerHTML = data; + } }, }); @@ -23,6 +27,7 @@ const TestComponent = defineComponent({ }, }), h('div', { 'data-test-id': 'paste' }, pasted.value), + h('div', { 'data-test-id': 'xss-attack', ref: htmlContent }), ]); }, }); @@ -68,4 +73,12 @@ describe('useClipboard()', () => { expect(pasteElement.textContent).toEqual(testValue); }); }); + + it('sanitizes HTML', async () => { + const unsafeHtml = 'https://www.ex.com/sfefdfdfdf/xdfef.json'; + const { getByTestId } = render(TestComponent); + + await userEvent.paste(unsafeHtml); + expect(within(getByTestId('xss-attack')).queryByRole('img')).not.toBeInTheDocument(); + }); }); diff --git a/packages/editor-ui/src/composables/useClipboard.ts b/packages/editor-ui/src/composables/useClipboard.ts index 1cd4c9ef709ec..113bfb17fe7b1 100644 --- a/packages/editor-ui/src/composables/useClipboard.ts +++ b/packages/editor-ui/src/composables/useClipboard.ts @@ -1,6 +1,7 @@ import { onBeforeUnmount, onMounted, ref } from 'vue'; import { useClipboard as useClipboardCore } from '@vueuse/core'; import { useDebounce } from '@/composables/useDebounce'; +import sanitizeHtml from 'sanitize-html'; type ClipboardEventFn = (data: string, event?: ClipboardEvent) => void; @@ -42,7 +43,7 @@ export function useClipboard( const clipboardData = event.clipboardData; if (clipboardData !== null) { - const clipboardValue = clipboardData.getData('text/plain'); + const clipboardValue = sanitizeHtml(clipboardData.getData('text/plain')); onPasteCallback.value(clipboardValue, event); } } From 38babf2e609373dedd6ade8c5ca151711663b8d5 Mon Sep 17 00:00:00 2001 From: r00gm Date: Fri, 13 Sep 2024 12:00:14 +0200 Subject: [PATCH 2/3] fix: sanitize messages --- .../editor-ui/src/composables/useClipboard.ts | 4 +-- .../editor-ui/src/composables/useMessage.ts | 31 +++++++++++++------ packages/editor-ui/src/utils/htmlUtils.ts | 7 +++++ 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/editor-ui/src/composables/useClipboard.ts b/packages/editor-ui/src/composables/useClipboard.ts index 113bfb17fe7b1..6bc2e11466f30 100644 --- a/packages/editor-ui/src/composables/useClipboard.ts +++ b/packages/editor-ui/src/composables/useClipboard.ts @@ -1,7 +1,7 @@ import { onBeforeUnmount, onMounted, ref } from 'vue'; import { useClipboard as useClipboardCore } from '@vueuse/core'; import { useDebounce } from '@/composables/useDebounce'; -import sanitizeHtml from 'sanitize-html'; +import { sanitizeInput } from '@/utils/htmlUtils'; type ClipboardEventFn = (data: string, event?: ClipboardEvent) => void; @@ -43,7 +43,7 @@ export function useClipboard( const clipboardData = event.clipboardData; if (clipboardData !== null) { - const clipboardValue = sanitizeHtml(clipboardData.getData('text/plain')); + const clipboardValue = sanitizeInput(clipboardData.getData('text/plain')); onPasteCallback.value(clipboardValue, event); } } diff --git a/packages/editor-ui/src/composables/useMessage.ts b/packages/editor-ui/src/composables/useMessage.ts index e24dc4579b706..897ebbdb95e61 100644 --- a/packages/editor-ui/src/composables/useMessage.ts +++ b/packages/editor-ui/src/composables/useMessage.ts @@ -1,5 +1,6 @@ import type { ElMessageBoxOptions, Action, MessageBoxInputData } from 'element-plus'; import { ElMessageBox as MessageBox } from 'element-plus'; +import { sanitizeInput } from '@/utils/htmlUtils'; export type MessageBoxConfirmResult = 'confirm' | 'cancel'; @@ -28,11 +29,13 @@ export function useMessage() { }; if (typeof configOrTitle === 'string') { - return await MessageBox.alert(message, configOrTitle, resolvedConfig).catch( + return await MessageBox.alert(sanitizeInput(message), configOrTitle, resolvedConfig).catch( handleCancelOrClose, ); } - return await MessageBox.alert(message, resolvedConfig).catch(handleCancelOrClose); + return await MessageBox.alert(sanitizeInput(message), resolvedConfig).catch( + handleCancelOrClose, + ); } async function confirm( @@ -50,12 +53,16 @@ export function useMessage() { }; if (typeof configOrTitle === 'string') { - return await MessageBox.confirm(message, configOrTitle, resolvedConfig).catch( - handleCancelOrClose, - ); + return await MessageBox.confirm( + sanitizeInput(message), + sanitizeInput(configOrTitle), + resolvedConfig, + ).catch(handleCancelOrClose); } - return await MessageBox.confirm(message, resolvedConfig).catch(handleCancelOrClose); + return await MessageBox.confirm(sanitizeInput(message), resolvedConfig).catch( + handleCancelOrClose, + ); } async function prompt( @@ -70,11 +77,15 @@ export function useMessage() { }; if (typeof configOrTitle === 'string') { - return await MessageBox.prompt(message, configOrTitle, resolvedConfig).catch( - handleCancelOrClosePrompt, - ); + return await MessageBox.prompt( + sanitizeInput(message), + sanitizeInput(configOrTitle), + resolvedConfig, + ).catch(handleCancelOrClosePrompt); } - return await MessageBox.prompt(message, resolvedConfig).catch(handleCancelOrClosePrompt); + return await MessageBox.prompt(sanitizeInput(message), resolvedConfig).catch( + handleCancelOrClosePrompt, + ); } return { diff --git a/packages/editor-ui/src/utils/htmlUtils.ts b/packages/editor-ui/src/utils/htmlUtils.ts index 5da34b396526b..6139374400e07 100644 --- a/packages/editor-ui/src/utils/htmlUtils.ts +++ b/packages/editor-ui/src/utils/htmlUtils.ts @@ -37,6 +37,13 @@ export function sanitizeHtml(dirtyHtml: string) { return sanitizedHtml; } +export const sanitizeInput = (message: T): string | T => { + if (typeof message === 'string') { + return sanitizeHtml(message); + } + return message; +}; + export function setPageTitle(title: string) { window.document.title = title; } From b9bb0101ffd38b4f8a5d3cf1eb13aeaac5ad62c8 Mon Sep 17 00:00:00 2001 From: r00gm Date: Fri, 13 Sep 2024 13:12:47 +0200 Subject: [PATCH 3/3] chore: rename and document function --- .../editor-ui/src/composables/useClipboard.ts | 4 ++-- .../editor-ui/src/composables/useMessage.ts | 18 +++++++++--------- packages/editor-ui/src/utils/htmlUtils.ts | 6 +++++- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/editor-ui/src/composables/useClipboard.ts b/packages/editor-ui/src/composables/useClipboard.ts index 6bc2e11466f30..9401b95c45cd1 100644 --- a/packages/editor-ui/src/composables/useClipboard.ts +++ b/packages/editor-ui/src/composables/useClipboard.ts @@ -1,7 +1,7 @@ import { onBeforeUnmount, onMounted, ref } from 'vue'; import { useClipboard as useClipboardCore } from '@vueuse/core'; import { useDebounce } from '@/composables/useDebounce'; -import { sanitizeInput } from '@/utils/htmlUtils'; +import { sanitizeIfString } from '@/utils/htmlUtils'; type ClipboardEventFn = (data: string, event?: ClipboardEvent) => void; @@ -43,7 +43,7 @@ export function useClipboard( const clipboardData = event.clipboardData; if (clipboardData !== null) { - const clipboardValue = sanitizeInput(clipboardData.getData('text/plain')); + const clipboardValue = sanitizeIfString(clipboardData.getData('text/plain')); onPasteCallback.value(clipboardValue, event); } } diff --git a/packages/editor-ui/src/composables/useMessage.ts b/packages/editor-ui/src/composables/useMessage.ts index 897ebbdb95e61..22bb50fc698c4 100644 --- a/packages/editor-ui/src/composables/useMessage.ts +++ b/packages/editor-ui/src/composables/useMessage.ts @@ -1,6 +1,6 @@ import type { ElMessageBoxOptions, Action, MessageBoxInputData } from 'element-plus'; import { ElMessageBox as MessageBox } from 'element-plus'; -import { sanitizeInput } from '@/utils/htmlUtils'; +import { sanitizeIfString } from '@/utils/htmlUtils'; export type MessageBoxConfirmResult = 'confirm' | 'cancel'; @@ -29,11 +29,11 @@ export function useMessage() { }; if (typeof configOrTitle === 'string') { - return await MessageBox.alert(sanitizeInput(message), configOrTitle, resolvedConfig).catch( + return await MessageBox.alert(sanitizeIfString(message), configOrTitle, resolvedConfig).catch( handleCancelOrClose, ); } - return await MessageBox.alert(sanitizeInput(message), resolvedConfig).catch( + return await MessageBox.alert(sanitizeIfString(message), resolvedConfig).catch( handleCancelOrClose, ); } @@ -54,13 +54,13 @@ export function useMessage() { if (typeof configOrTitle === 'string') { return await MessageBox.confirm( - sanitizeInput(message), - sanitizeInput(configOrTitle), + sanitizeIfString(message), + sanitizeIfString(configOrTitle), resolvedConfig, ).catch(handleCancelOrClose); } - return await MessageBox.confirm(sanitizeInput(message), resolvedConfig).catch( + return await MessageBox.confirm(sanitizeIfString(message), resolvedConfig).catch( handleCancelOrClose, ); } @@ -78,12 +78,12 @@ export function useMessage() { if (typeof configOrTitle === 'string') { return await MessageBox.prompt( - sanitizeInput(message), - sanitizeInput(configOrTitle), + sanitizeIfString(message), + sanitizeIfString(configOrTitle), resolvedConfig, ).catch(handleCancelOrClosePrompt); } - return await MessageBox.prompt(sanitizeInput(message), resolvedConfig).catch( + return await MessageBox.prompt(sanitizeIfString(message), resolvedConfig).catch( handleCancelOrClosePrompt, ); } diff --git a/packages/editor-ui/src/utils/htmlUtils.ts b/packages/editor-ui/src/utils/htmlUtils.ts index 6139374400e07..2a78582b016a1 100644 --- a/packages/editor-ui/src/utils/htmlUtils.ts +++ b/packages/editor-ui/src/utils/htmlUtils.ts @@ -37,7 +37,11 @@ export function sanitizeHtml(dirtyHtml: string) { return sanitizedHtml; } -export const sanitizeInput = (message: T): string | T => { +/** + * Checks if the input is a string and sanitizes it by removing or escaping harmful characters, + * returning the original input if it's not a string. + */ +export const sanitizeIfString = (message: T): string | T => { if (typeof message === 'string') { return sanitizeHtml(message); }