Skip to content

Commit 996a325

Browse files
authored
Merge pull request #3578 from RedisInsight/fe/feature/RI-5889-sanitize
#RI-5889 - sanitize markdown
2 parents 52d7c9c + 7101494 commit 996a325

File tree

4 files changed

+75
-1
lines changed

4 files changed

+75
-1
lines changed

redisinsight/ui/src/services/formatter/MarkdownToJsxString.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
remarkLink,
1111
rehypeLinks,
1212
remarkImage,
13-
remarkCode
13+
remarkCode,
14+
remarkSanitize,
1415
} from 'uiSrc/utils/formatters/markdown'
1516
import { IFormatter, IFormatterConfig } from './formatter.interfaces'
1617

@@ -20,6 +21,7 @@ class MarkdownToJsxString implements IFormatter {
2021
return new Promise((resolve, reject) => {
2122
unified()
2223
.use(remarkParse)
24+
.use(remarkSanitize)
2325
.use(remarkGfm) // support GitHub Flavored Markdown
2426
.use(remarkRedisUpload, path) // Add custom component for redis-upload code block
2527
.use(remarkCode, codeOptions) // Add custom component for Redis code block

redisinsight/ui/src/utils/formatters/markdown/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { rehypeLinks } from './rehypeLinks'
22
import { remarkImage } from './remarkImage'
33
import { remarkLink } from './remarkLink'
44
import { remarkCode } from './remarkCode'
5+
import { remarkSanitize } from './remarkSanitize'
56
import { remarkRedisUpload } from './remarkRedisUpload'
67

78
export {
@@ -10,4 +11,5 @@ export {
1011
remarkLink,
1112
remarkCode,
1213
remarkRedisUpload,
14+
remarkSanitize,
1315
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { visit } from 'unist-util-visit'
2+
3+
const dangerousAttributes = [
4+
'onabort', 'onafterprint', 'onanimationend', 'onanimationiteration', 'onanimationstart',
5+
'onbeforeprint', 'onbeforeunload', 'onblur', 'oncancel', 'oncanplay', 'oncanplaythrough',
6+
'onchange', 'onclick', 'onclose', 'oncontextmenu', 'oncopy', 'oncuechange', 'oncut', 'ondblclick',
7+
'ondrag', 'ondragend', 'ondragenter', 'ondragexit', 'ondragleave', 'ondragover', 'ondragstart',
8+
'ondrop', 'ondurationchange', 'onemptied', 'onended', 'onerror', 'onfocus', 'onfocusin', 'onfocusout',
9+
'onformdata', 'onhashchange', 'oninput', 'oninvalid', 'onkeydown', 'onkeypress', 'onkeyup',
10+
'onlanguagechange', 'onload', 'onloadeddata', 'onloadedmetadata', 'onloadstart', 'onmessage',
11+
'onmessageerror', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout',
12+
'onmouseover', 'onmouseup', 'onoffline', 'ononline', 'onpagehide', 'onpageshow', 'onpaste',
13+
'onpause', 'onplay', 'onplaying', 'onpopstate', 'onprogress', 'onratechange', 'onrejectionhandled',
14+
'onreset', 'onresize', 'onscroll', 'onsearch', 'onseeked', 'onseeking', 'onselect', 'onstalled',
15+
'onstorage', 'onsubmit', 'onsuspend', 'ontimeupdate', 'ontoggle', 'ontransitionend', 'onunhandledrejection',
16+
'onunload', 'onvolumechange', 'onwaiting', 'onwheel', 'href', 'src', 'action', 'formaction', 'manifest',
17+
'background', 'poster', 'cite', 'data', 'ping', 'xlink:href', 'style', 'srcdoc', 'sandbox'
18+
].join('|')
19+
20+
export const remarkSanitize = (): (tree: Node) => void => (tree: any) => {
21+
visit(tree, 'html', (node) => {
22+
const dangerousAttrRegex = new RegExp(`\\s*(${dangerousAttributes})="[^"]*"`, 'gi')
23+
24+
if (node.value.match(dangerousAttrRegex)) {
25+
node.value = node.value.replace(dangerousAttrRegex, (match: string) => {
26+
const attr = match.toLowerCase().trim()
27+
if (attr.startsWith('href') || attr.startsWith('src') || attr.startsWith('xlink:href')) {
28+
if (attr.indexOf('"javascript:') > -1) return ''
29+
return match
30+
}
31+
32+
return ''
33+
})
34+
}
35+
})
36+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { visit } from 'unist-util-visit'
2+
import { remarkSanitize } from 'uiSrc/utils/formatters/markdown'
3+
4+
jest.mock('unist-util-visit')
5+
6+
const testCases = [
7+
{ input: '', output: '' },
8+
{ input: '<a href="https://localhost">', output: '<a href="https://localhost">' },
9+
{ input: '<a href="javascript:alert(1)">', output: '<a>' },
10+
{ input: '<img onload="alert(1)">', output: '<img>' },
11+
{ input: '<img src="javascript:alert(1)">', output: '<img>' },
12+
{ input: '<img src="img.png">', output: '<img src="img.png">' },
13+
]
14+
15+
describe('remarkSanitize', () => {
16+
testCases.forEach((tc) => {
17+
it('should return proper sanitized value', () => {
18+
const node = {
19+
value: tc.input
20+
};
21+
22+
// mock implementation
23+
(visit as jest.Mock)
24+
.mockImplementation((_tree: any, _name: string, callback: (node: any) => void) => { callback(node) })
25+
26+
const remark = remarkSanitize()
27+
remark({} as Node)
28+
expect(node).toEqual({
29+
...node,
30+
value: tc.output,
31+
})
32+
})
33+
})
34+
})

0 commit comments

Comments
 (0)