fix(security-markdown-widget): allow sanitization of preview content (#4886)

This commit is contained in:
Erez Rokah 2021-01-29 06:50:48 -08:00 committed by GitHub
parent da31332641
commit 27aec85550
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 4 deletions

View File

@ -22,6 +22,7 @@
"build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward"
},
"dependencies": {
"dompurify": "^2.2.6",
"is-hotkey": "^0.2.0",
"mdast-util-definitions": "^1.2.3",
"mdast-util-to-string": "^1.0.5",

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { WidgetPreviewContainer } from 'netlify-cms-ui-default';
import { markdownToHtml } from './serializers';
import DOMPurify from 'dompurify';
class MarkdownPreview extends React.Component {
static propTypes = {
getAsset: PropTypes.func.isRequired,
@ -11,14 +11,15 @@ class MarkdownPreview extends React.Component {
};
render() {
const { value, getAsset, resolveWidget } = this.props;
const { value, getAsset, resolveWidget, field } = this.props;
if (value === null) {
return null;
}
const html = markdownToHtml(value, { getAsset, resolveWidget });
const toRender = field?.get('sanitize_preview', false) ? DOMPurify.sanitize(html) : html;
return <WidgetPreviewContainer dangerouslySetInnerHTML={{ __html: html }} />;
return <WidgetPreviewContainer dangerouslySetInnerHTML={{ __html: toRender }} />;
}
}

View File

@ -15,6 +15,36 @@ exports[`Markdown Preview renderer HTML rendering should render HTML 1`] = `
/>
`;
exports[`Markdown Preview renderer HTML sanitization should not sanitize HTML 1`] = `
.emotion-0 {
margin: 15px 2px;
}
<div
className="emotion-0 emotion-1"
dangerouslySetInnerHTML={
Object {
"__html": "<img src=\\"foobar.png\\" onerror=\\"alert('hello')\\">",
}
}
/>
`;
exports[`Markdown Preview renderer HTML sanitization should sanitize HTML 1`] = `
.emotion-0 {
margin: 15px 2px;
}
<div
className="emotion-0 emotion-1"
dangerouslySetInnerHTML={
Object {
"__html": "<img src=\\"foobar.png\\">",
}
}
/>
`;
exports[`Markdown Preview renderer Markdown rendering Code should render code 1`] = `
.emotion-0 {
margin: 15px 2px;

View File

@ -1,6 +1,7 @@
import React from 'react';
import { create, act } from 'react-test-renderer';
import { padStart } from 'lodash';
import { Map } from 'immutable';
import MarkdownPreview from '../MarkdownPreview';
import { markdownToHtml } from '../serializers';
@ -216,4 +217,44 @@ I get 10 times more traffic from [Google] than from [Yahoo] or [MSN].
expect(root.toJSON()).toMatchSnapshot();
});
});
describe('HTML sanitization', () => {
it('should sanitize HTML', async () => {
const value = `<img src="foobar.png" onerror="alert('hello')">`;
const field = Map({ sanitize_preview: true });
let root;
await act(async () => {
root = create(
<MarkdownPreview
value={value}
getAsset={jest.fn()}
resolveWidget={jest.fn()}
field={field}
/>,
);
});
expect(root.toJSON()).toMatchSnapshot();
});
it('should not sanitize HTML', async () => {
const value = `<img src="foobar.png" onerror="alert('hello')">`;
const field = Map({ sanitize_preview: false });
let root;
await act(async () => {
root = create(
<MarkdownPreview
value={value}
getAsset={jest.fn()}
resolveWidget={jest.fn()}
field={field}
/>,
);
});
expect(root.toJSON()).toMatchSnapshot();
});
});
});

View File

@ -16,6 +16,7 @@ The markdown widget provides a full fledged text editor allowing users to format
* `buttons`: an array of strings representing the formatting buttons to display (all shown by default). Buttons include: `bold`, `italic`, `code`, `link`, `heading-one`, `heading-two`, `heading-three`, `heading-four`, `heading-five`, `heading-six`, `quote`, `bulleted-list`, and `numbered-list`.
* `editor_components`: an array of strings representing the names of editor components to display (all shown by default). Netlify CMS includes `image` and `code-block` editor components by default, and custom components may be [created and registered](/docs/custom-widgets/#registereditorcomponent).
* `modes`: an array of strings representing the names of allowed editor modes. Possible modes are `raw` and `rich_text`. A toggle button appears in the toolbar when more than one mode is available.
* `sanitize_preview`: accepts a boolean value, `false` by default. Sanitizes markdown preview to prevent XSS attacks - might alter the preview content.
* **Example:**
```yaml
@ -26,4 +27,4 @@ This would render as:
![Markdown widget example](/img/widgets-markdown.png)
*Please note:* The markdown widget outputs a raw markdown string. Your static site generator may or may not render the markdown to HTML automatically. Consult with your static site generator's documentation for more information about rendering markdown.
*Please note:* The markdown widget outputs a raw markdown string. Your static site generator may or may not render the markdown to HTML automatically. Consult with your static site generator's documentation for more information about rendering markdown.

View File

@ -7313,6 +7313,11 @@ domhandler@^2.3.0:
dependencies:
domelementtype "1"
dompurify@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.6.tgz#54945dc5c0b45ce5ae228705777e8e59d7b2edc4"
integrity sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ==
domutils@^1.5.1, domutils@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"