From e24a3068a4e47c8a0ef7bcfbb585ef2490728bee Mon Sep 17 00:00:00 2001 From: Julio Sgarbi Date: Wed, 3 Jan 2024 09:21:14 -0300 Subject: [PATCH] fix: LEAP-444: Implement HTML sanitization in LSF (#1651) * fix: LEAP-444: Implement HTML sanitization in LSF --- src/components/App/App.js | 3 +- src/components/ErrorMessage/ErrorMessage.js | 3 +- .../InstructionsModal/InstructionsModal.tsx | 3 +- src/tags/control/Choice.js | 3 +- src/tags/control/Label.js | 3 +- src/tags/visual/Style.js | 3 +- src/utils/__tests__/html.test.ts | 35 +++++++++++++++++++ src/utils/feature-flags.ts | 1 - src/utils/html.js | 11 +++++- 9 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 src/utils/__tests__/html.test.ts diff --git a/src/components/App/App.js b/src/components/App/App.js index 463f90498b..845e39838e 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.js @@ -48,6 +48,7 @@ import { FF_DEV_1170, FF_DEV_3873, FF_LSDV_4620_3_ML, isFF } from '../../utils/f import { Annotation } from './Annotation'; import { Button } from '../../common/Button/Button'; import { reactCleaner } from '../../utils/reactCleaner'; +import { sanitizeHtml } from '../../utils/html'; /** * App @@ -241,7 +242,7 @@ class App extends Component { <> {store.showingDescription && ( -
+
)} diff --git a/src/components/ErrorMessage/ErrorMessage.js b/src/components/ErrorMessage/ErrorMessage.js index e4e2922901..a679e34099 100644 --- a/src/components/ErrorMessage/ErrorMessage.js +++ b/src/components/ErrorMessage/ErrorMessage.js @@ -1,9 +1,10 @@ import React from 'react'; import styles from './ErrorMessage.module.scss'; +import { sanitizeHtml } from '../../utils/html'; export const ErrorMessage = ({ error }) => { if (typeof error === 'string') { - return
; + return
; } const body = error instanceof Error ? error.message : error; diff --git a/src/components/InstructionsModal/InstructionsModal.tsx b/src/components/InstructionsModal/InstructionsModal.tsx index a967b4cf85..efcced2ef4 100644 --- a/src/components/InstructionsModal/InstructionsModal.tsx +++ b/src/components/InstructionsModal/InstructionsModal.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Modal } from 'antd'; +import { sanitizeHtml } from '../../utils/html'; export const InstructionsModal = ({ title, @@ -50,7 +51,7 @@ export const InstructionsModal = ({ {typeof children === 'string' ? (

) : (

{children}

diff --git a/src/tags/control/Choice.js b/src/tags/control/Choice.js index e4868136f1..8cac33b0ba 100644 --- a/src/tags/control/Choice.js +++ b/src/tags/control/Choice.js @@ -17,6 +17,7 @@ import { Block, Elem } from '../../utils/bem'; import './Choice/Choice.styl'; import { LsChevron } from '../../assets/icons'; import { HintTooltip } from '../../components/Taxonomy/Taxonomy'; +import { sanitizeHtml } from '../../utils/html'; /** * The `Choice` tag represents a single choice for annotations. Use with the `Choices` tag or `Taxonomy` tag to provide specific choice options. @@ -207,7 +208,7 @@ const HtxNewChoiceView = ({ item, store }) => { onChange={changeHandler} > - {item.html ? : item._value } + {item.html ? : item._value } {showHotkey && ([{item.hotkey}])} diff --git a/src/tags/control/Label.js b/src/tags/control/Label.js index 26e7fe72a0..0d3b89cbad 100644 --- a/src/tags/control/Label.js +++ b/src/tags/control/Label.js @@ -18,6 +18,7 @@ import ToolsManager from '../../tools/Manager'; import Utils from '../../utils'; import { parseValue } from '../../utils/data'; import { FF_DEV_2128, isFF } from '../../utils/feature-flags'; +import { sanitizeHtml } from '../../utils/html'; /** * The `Label` tag represents a single label. Use with the `Labels` tag, including `BrushLabels`, `EllipseLabels`, `HyperTextLabels`, `KeyPointLabels`, and other `Labels` tags to specify the value of a specific label. @@ -304,7 +305,7 @@ const HtxLabelView = inject('store')( selected={item.selected} onClick={item.onClick} > - {item.html ?
: item._value } + {item.html ?
: item._value } {item.showalias === true && item.alias && (  {item.alias} )} diff --git a/src/tags/visual/Style.js b/src/tags/visual/Style.js index 068e170d8c..5ee4b5553d 100644 --- a/src/tags/visual/Style.js +++ b/src/tags/visual/Style.js @@ -4,6 +4,7 @@ import { observer } from 'mobx-react'; import Registry from '../../core/Registry'; import { guidGenerator } from '../../utils/unique'; +import { sanitizeHtml } from '../../utils/html'; /** * The `Style` tag is used in combination with the View tag to apply custom CSS properties to the labeling interface. See the [CSS Reference](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference) on the MDN page for a full list of available properties that you can reference. You can also adjust default Label Studio CSS classes. Use the browser developer tools to inspect the element on the UI and locate the class name, then specify that class name in the `Style` tag. @@ -69,7 +70,7 @@ const Model = types.model({ const StyleModel = types.compose('StyleModel', Model); const HtxStyle = observer(({ item }) => { - return ; + return ; }); Registry.addTag('style', StyleModel, HtxStyle); diff --git a/src/utils/__tests__/html.test.ts b/src/utils/__tests__/html.test.ts new file mode 100644 index 0000000000..7c8b3cb673 --- /dev/null +++ b/src/utils/__tests__/html.test.ts @@ -0,0 +1,35 @@ +import { sanitizeHtml } from '../html'; + +const htmlSanitizeList = [ + { + input: '', + expected: '', + },{ + input: '', + expected: '', + }, { + input: '">', + expected: '">', + },{ + input: '', + expected: '', + },{ + input: '>', + expected: '>', + },{ + input: 'alert(\'XSS\');]]>', + expected: '', + },{ + input: 'It\'s a test to check if <, > and & are escaped', + expected: 'It\'s a test to check if <, > and & are escaped', + }, +]; + + +describe('Helper function html sanitize', () => { + test('sanitize html list', () => { + htmlSanitizeList.forEach((item) => { + expect(sanitizeHtml(item.input)).toBe(item.expected); + }); + }); +}); diff --git a/src/utils/feature-flags.ts b/src/utils/feature-flags.ts index 5619becc3d..922cf6d2fb 100644 --- a/src/utils/feature-flags.ts +++ b/src/utils/feature-flags.ts @@ -333,7 +333,6 @@ export const FF_ZOOM_OPTIM = 'fflag_fix_front_leap_32_zoom_perf_190923_short'; export const FF_SAFE_TEXT = 'fflag_fix_leap_466_text_sanitization'; - Object.assign(window, { APP_SETTINGS: { ...(window.APP_SETTINGS ?? {}), diff --git a/src/utils/html.js b/src/utils/html.js index 2fc29e971d..3e1b9c1a42 100644 --- a/src/utils/html.js +++ b/src/utils/html.js @@ -540,11 +540,20 @@ function sanitizeHtml(html = []) { 'ontimeupdate', 'ontoggle', 'onunhandledrejection', 'onunload', 'onvolumechange', 'onwaiting', 'onwheel']; + const disallowedTags = { + 'script': true, + 'iframe': true, + }; + return sanitizeHTML(html, { allowedTags: false, allowedAttributes: false, disallowedTagsMode: 'discard', - disallowedTags: ['script', 'iframe'], + allowVulnerableTags: true, + exclusiveFilter(frame) { + //...except those in the blacklist + return disallowedTags[frame.tag]; + }, nonTextTags: ['script', 'textarea', 'option', 'noscript'], transformTags: { '*': (tagName, attribs) => {