Skip to content

Commit

Permalink
fix: LEAP-444: Implement HTML sanitization in LSF (HumanSignal#1651)
Browse files Browse the repository at this point in the history
* fix: LEAP-444: Implement HTML sanitization in LSF
  • Loading branch information
juliosgarbi authored and MasherJames committed Feb 29, 2024
1 parent 5489123 commit ac810f7
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 8 deletions.
3 changes: 2 additions & 1 deletion src/components/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -241,7 +242,7 @@ class App extends Component {
<>
{store.showingDescription && (
<Segment>
<div dangerouslySetInnerHTML={{ __html: store.description }} />
<div dangerouslySetInnerHTML={{ __html: sanitizeHtml(store.description) }} />
</Segment>
)}
</>
Expand Down
3 changes: 2 additions & 1 deletion src/components/ErrorMessage/ErrorMessage.js
Original file line number Diff line number Diff line change
@@ -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 <div className={styles.error} dangerouslySetInnerHTML={{ __html: error }} />;
return <div className={styles.error} dangerouslySetInnerHTML={{ __html: sanitizeHtml(error) }} />;
}
const body = error instanceof Error ? error.message : error;

Expand Down
3 changes: 2 additions & 1 deletion src/components/InstructionsModal/InstructionsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { Modal } from 'antd';
import { sanitizeHtml } from '../../utils/html';

export const InstructionsModal = ({
title,
Expand Down Expand Up @@ -50,7 +51,7 @@ export const InstructionsModal = ({
{typeof children === 'string' ? (
<p
style={contentStyle}
dangerouslySetInnerHTML={{ __html: children }}
dangerouslySetInnerHTML={{ __html: sanitizeHtml(children) }}
/>
) : (
<p style={contentStyle}>{children}</p>
Expand Down
3 changes: 2 additions & 1 deletion src/tags/control/Choice.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -207,7 +208,7 @@ const HtxNewChoiceView = ({ item, store }) => {
onChange={changeHandler}
>
<HintTooltip title={item.hint} wrapper="span">
{item.html ? <span dangerouslySetInnerHTML={{ __html: item.html }}/> : item._value }
{item.html ? <span dangerouslySetInnerHTML={{ __html: sanitizeHtml(item.html) }}/> : item._value }
{showHotkey && (<Hint>[{item.hotkey}]</Hint>)}
</HintTooltip>
</Elem>
Expand Down
3 changes: 2 additions & 1 deletion src/tags/control/Label.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -304,7 +305,7 @@ const HtxLabelView = inject('store')(
selected={item.selected}
onClick={item.onClick}
>
{item.html ? <div title={item._value} dangerouslySetInnerHTML={{ __html: item.html }}/> : item._value }
{item.html ? <div title={item._value} dangerouslySetInnerHTML={{ __html: sanitizeHtml(item.html) }}/> : item._value }
{item.showalias === true && item.alias && (
<span style={Utils.styleToProp(item.aliasstyle)}>&nbsp;{item.alias}</span>
)}
Expand Down
3 changes: 2 additions & 1 deletion src/tags/visual/Style.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -69,7 +70,7 @@ const Model = types.model({
const StyleModel = types.compose('StyleModel', Model);

const HtxStyle = observer(({ item }) => {
return <style dangerouslySetInnerHTML={{ __html: item.value }}></style>;
return <style dangerouslySetInnerHTML={{ __html: sanitizeHtml(item.value) }}></style>;
});

Registry.addTag('style', StyleModel, HtxStyle);
Expand Down
35 changes: 35 additions & 0 deletions src/utils/__tests__/html.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { sanitizeHtml } from '../html';

const htmlSanitizeList = [
{
input: '<iframe src="http://malicious.com"></iframe>',
expected: '',
},{
input: '<script>alert(\'XSS\');</script>',
expected: '',
}, {
input: '"><img src=x onerror=alert(\'XSS\')>',
expected: '"&gt;<img src="x" />',
},{
input: '<script>alert(1)</script foo=\'bar\'>',
expected: '',
},{
input: '><script>alert(\'XSS\')</script>',
expected: '&gt;',
},{
input: '<?xml version="1.0" encoding="ISO-8859-1"?><foo><![CDATA[<script>alert(\'XSS\');</script>]]></foo>',
expected: '<foo></foo>',
},{
input: 'It\'s a test to check if <, > and & are escaped',
expected: 'It\'s a test to check if &lt;, &gt; and &amp; are escaped',
},
];


describe('Helper function html sanitize', () => {
test('sanitize html list', () => {
htmlSanitizeList.forEach((item) => {
expect(sanitizeHtml(item.input)).toBe(item.expected);
});
});
});
1 change: 0 additions & 1 deletion src/utils/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? {}),
Expand Down
11 changes: 10 additions & 1 deletion src/utils/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down

0 comments on commit ac810f7

Please sign in to comment.