Skip to content

Commit 364aa07

Browse files
authored
Sanitize user/database-derived html properly before display (#1731)
* add dompurify and apply to all dangerouslySetInnerHTML * ban img tags to keep behaviour the same
1 parent c998e9d commit 364aa07

21 files changed

Lines changed: 94 additions & 33 deletions

File tree

packages/libs/coreui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"dependencies": {
3434
"@react-hook/size": "^2.1.2",
3535
"core-js": "^3.25.3",
36+
"dompurify": "^3.4.7",
3637
"lodash": "^4.17.21",
3738
"react-cool-dimensions": "^2.0.7",
3839
"react-draggable": "^4.4.5",

packages/libs/coreui/src/components/Mesa/Components/MesaTooltip.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { CSSProperties } from 'react';
2+
import DOMPurify from 'dompurify';
23

34
import { Tooltip } from '../../info/Tooltip';
45

@@ -54,7 +55,11 @@ const MesaTooltip = ({
5455
<Tooltip
5556
title={
5657
renderHtml ? (
57-
<div dangerouslySetInnerHTML={{ __html: content as string }} />
58+
<div
59+
dangerouslySetInnerHTML={{
60+
__html: DOMPurify.sanitize(content as string),
61+
}}
62+
/>
5863
) : (
5964
content ?? <></>
6065
)

packages/libs/coreui/src/components/Mesa/Templates.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import DOMPurify from 'dompurify';
23

34
import { OptionsDefaults } from './Defaults';
45
import OverScroll from './Components/OverScroll';
@@ -35,7 +36,9 @@ const Templates = {
3536
let { displayText, url } = value;
3637
let href = url ? url : '#';
3738
let text = displayText.length ? value.displayText : href;
38-
text = <div dangerouslySetInnerHTML={{ __html: text }} />;
39+
text = (
40+
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(text) }} />
41+
);
3942
let target = '_blank';
4043

4144
const props = { href, target, className };
@@ -59,7 +62,9 @@ const Templates = {
5962
htmlCell({ key, value, row, rowIndex, column }) {
6063
const { truncated } = column;
6164
const className = 'Cell HtmlCell Cell-' + key;
62-
const content = <div dangerouslySetInnerHTML={{ __html: value }} />;
65+
const content = (
66+
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(value) }} />
67+
);
6368
const size = truncated === true ? '16em' : truncated;
6469

6570
return truncated ? (

packages/libs/coreui/src/components/inputs/SelectTree/Utils.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'regenerator-runtime/runtime';
2+
import DOMPurify from 'dompurify';
23
// Field types
34
// -----------
45

@@ -384,13 +385,10 @@ export function safeHtml<P>(
384385
if (str.indexOf('<') === -1) {
385386
return <Component {...props}>{str}</Component>;
386387
}
387-
// Use innerHTML to auto close tags
388-
const container = document.createElement('div');
389-
container.innerHTML = str;
390388
return (
391389
<Component
392390
{...props}
393-
dangerouslySetInnerHTML={{ __html: container.innerHTML }}
391+
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(str) }}
394392
/>
395393
);
396394
}

packages/libs/preferred-organisms/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@veupathdb/coreui": "workspace:^",
1919
"@veupathdb/wdk-client": "workspace:^",
2020
"@veupathdb/web-common": "workspace:^",
21+
"dompurify": "^3.4.7",
2122
"lodash": "^4.17.21",
2223
"recoil": "^0.7.7"
2324
},

packages/libs/preferred-organisms/src/lib/hooks/maxRecommendedGate.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useCallback } from 'react';
2+
import DOMPurify from 'dompurify';
23
import { Dialog } from '@veupathdb/wdk-client/lib/Components';
34
import {
45
FilledButton,
@@ -99,7 +100,11 @@ export function useMaxRecommendedGate(
99100
<div className="MaxRecommendedGate">
100101
<div className="MaxRecommendedGate--Message">
101102
{customMessage ? (
102-
<div dangerouslySetInnerHTML={{ __html: customMessage }} />
103+
<div
104+
dangerouslySetInnerHTML={{
105+
__html: DOMPurify.sanitize(customMessage),
106+
}}
107+
/>
103108
) : (
104109
defaultMessage
105110
)}

packages/libs/wdk-client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"@veupathdb/components": "workspace:^",
2626
"@veupathdb/coreui": "workspace:^",
2727
"classnames": "^2.2.0",
28+
"dompurify": "^3.4.7",
2829
"history": "^4.10.1",
2930
"json-stable-stringify": "^1.0.0",
3031
"localforage": "^1.5.0",

packages/libs/wdk-client/src/Utils/ComponentUtils.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useEffect } from 'react';
2+
import DOMPurify from 'dompurify';
23
import { AttributeValue } from '../Utils/WdkModel';
34
import { stripHTML } from './DomUtils';
45

@@ -267,13 +268,10 @@ export function safeHtml<P>(
267268
if (str.indexOf('<') === -1 && !isHtmlEntityFound) {
268269
return <Component {...props}>{str}</Component>;
269270
}
270-
// Use innerHTML to auto close tags
271-
let container = document.createElement('div');
272-
container.innerHTML = str;
273271
return (
274272
<Component
275273
{...props}
276-
dangerouslySetInnerHTML={{ __html: container.innerHTML }}
274+
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(str) }}
277275
/>
278276
);
279277
}

packages/libs/wdk-client/src/Views/ResultTableSummaryView/AttributeCell.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
22
import { isEqual, truncate } from 'lodash';
3+
import DOMPurify from 'dompurify';
34
import { RecordInstance, AttributeField } from '../../Utils/WdkModel';
45
import { safeHtml, wrappable } from '../../Utils/ComponentUtils';
56
import { Tooltip } from '@veupathdb/coreui';
@@ -78,7 +79,7 @@ function AttributeCellInner({ attribute, recordInstance }: AttributeCellProps) {
7879
<a
7980
href={url}
8081
dangerouslySetInnerHTML={{
81-
__html: truncatedDisplay,
82+
__html: DOMPurify.sanitize(truncatedDisplay),
8283
}}
8384
/>
8485
</div>

packages/libs/web-common/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@veupathdb/eda": "workspace:^",
3838
"@veupathdb/wdk-client": "workspace:^",
3939
"custom-event-polyfill": "^1.0.7",
40+
"dompurify": "^3.4.7",
4041
"md5": "^2.3.0",
4142
"pluralize": "^8.0.0",
4243
"whatwg-fetch": "^3.5.0"

0 commit comments

Comments
 (0)