From 27aec85550c0be52c55f7b33314ecd52727fdcb5 Mon Sep 17 00:00:00 2001 From: Erez Rokah Date: Fri, 29 Jan 2021 06:50:48 -0800 Subject: [PATCH] fix(security-markdown-widget): allow sanitization of preview content (#4886) --- .../netlify-cms-widget-markdown/package.json | 1 + .../src/MarkdownPreview.js | 7 ++-- .../__snapshots__/renderer.spec.js.snap | 30 ++++++++++++++ .../src/__tests__/renderer.spec.js | 41 +++++++++++++++++++ website/content/docs/widgets/markdown.md | 3 +- yarn.lock | 5 +++ 6 files changed, 83 insertions(+), 4 deletions(-) diff --git a/packages/netlify-cms-widget-markdown/package.json b/packages/netlify-cms-widget-markdown/package.json index a62cd182..1146626a 100644 --- a/packages/netlify-cms-widget-markdown/package.json +++ b/packages/netlify-cms-widget-markdown/package.json @@ -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", diff --git a/packages/netlify-cms-widget-markdown/src/MarkdownPreview.js b/packages/netlify-cms-widget-markdown/src/MarkdownPreview.js index a6f672e5..8f73c56d 100644 --- a/packages/netlify-cms-widget-markdown/src/MarkdownPreview.js +++ b/packages/netlify-cms-widget-markdown/src/MarkdownPreview.js @@ -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 ; + return ; } } diff --git a/packages/netlify-cms-widget-markdown/src/__tests__/__snapshots__/renderer.spec.js.snap b/packages/netlify-cms-widget-markdown/src/__tests__/__snapshots__/renderer.spec.js.snap index 5e1dbf1f..6ced837f 100644 --- a/packages/netlify-cms-widget-markdown/src/__tests__/__snapshots__/renderer.spec.js.snap +++ b/packages/netlify-cms-widget-markdown/src/__tests__/__snapshots__/renderer.spec.js.snap @@ -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; +} + +
", + } + } +/> +`; + +exports[`Markdown Preview renderer HTML sanitization should sanitize HTML 1`] = ` +.emotion-0 { + margin: 15px 2px; +} + +
", + } + } +/> +`; + exports[`Markdown Preview renderer Markdown rendering Code should render code 1`] = ` .emotion-0 { margin: 15px 2px; diff --git a/packages/netlify-cms-widget-markdown/src/__tests__/renderer.spec.js b/packages/netlify-cms-widget-markdown/src/__tests__/renderer.spec.js index fda890cc..48d4192c 100644 --- a/packages/netlify-cms-widget-markdown/src/__tests__/renderer.spec.js +++ b/packages/netlify-cms-widget-markdown/src/__tests__/renderer.spec.js @@ -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 = ``; + const field = Map({ sanitize_preview: true }); + + let root; + await act(async () => { + root = create( + , + ); + }); + + expect(root.toJSON()).toMatchSnapshot(); + }); + + it('should not sanitize HTML', async () => { + const value = ``; + const field = Map({ sanitize_preview: false }); + + let root; + await act(async () => { + root = create( + , + ); + }); + + expect(root.toJSON()).toMatchSnapshot(); + }); + }); }); diff --git a/website/content/docs/widgets/markdown.md b/website/content/docs/widgets/markdown.md index 9a114b0e..defc51f3 100644 --- a/website/content/docs/widgets/markdown.md +++ b/website/content/docs/widgets/markdown.md @@ -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. \ No newline at end of file +*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. diff --git a/yarn.lock b/yarn.lock index a2ed5b07..fc5d8526 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"