From 28d7fe15ea326ae7d31a369e894474a1da0b70f3 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 6 Aug 2021 08:35:09 +0200 Subject: [PATCH 1/6] Add DOMPurify to sanitize HTML Non breacking new feature (disabled by default) --- README.md | 22 ++++++++++++++++++++++ index.js | 1 + lib/renderer.js | 16 +++++++++++++--- package.json | 5 +++-- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b57f31d..79e9f6f 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ marked: autolink: true mangle: true sanitizeUrl: false + sanitize; false, headerIds: true lazyload: false prependRoot: false @@ -61,6 +62,7 @@ marked: - **mangle** - Escape autolinked email address with HTML character references. * This is to obscure email address from _basic_ crawler used by spam bot, while still readable to web browsers. - **sanitizeUrl** - Remove URLs that start with `javascript:`, `vbscript:` and `data:`. +- **sanitize** - Enable [DOMPurify](https://github.com/cure53/DOMPurify) to be run on the rendered Markdown. See below for configuration - **headerIds** - Insert header id, e.g. `

text

`. Useful for inserting anchor link to each paragraph with a heading. - **anchorAlias** - Enables custom header id * Example: `## [foo](#bar)`, id will be set as "bar". @@ -91,6 +93,26 @@ For more options, see [Marked](https://marked.js.org/using_advanced#options). Du ## Extras +### Sanitize HTML with DOMPurify + +[DOMPurify](https://github.com/cure53/DOMPurify) can be enabled to sanitize the rendered HTML. + +To enable it, pass an object containing the DomPurify options: + +```json +sanitize: true +``` + +Or you can enable specific DOMPurify options (but according to DOMPurify authors, the default options are safe): + +```yml +sanitize: + FORBID_TAGS: + - "style" +``` + +See https://github.com/cure53/DOMPurify#can-i-configure-dompurify for a full reference of available options + ### Definition/Description Lists `hexo-renderer-marked` also implements description/definition lists using the same syntax as [PHP Markdown Extra][PHP Markdown Extra]. diff --git a/index.js b/index.js index 43b1202..32966cc 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,7 @@ hexo.config.marked = Object.assign({ autolink: true, mangle: true, sanitizeUrl: false, + sanitize: false, headerIds: true, anchorAlias: false, lazyload: false, diff --git a/lib/renderer.js b/lib/renderer.js index 865de36..595708d 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -1,6 +1,7 @@ 'use strict'; const marked = require('marked'); +const domPurify = require('dompurify'); const { escape } = require('marked/src/helpers'); const { encodeURL, slugize, stripHTML, url_for, isExternalLink } = require('hexo-util'); const MarkedRenderer = marked.Renderer; @@ -235,7 +236,7 @@ class Tokenizer extends MarkedTokenizer { } module.exports = function(data, options) { - const { post_asset_folder, marked: markedCfg, source_dir } = this.config; + const { post_asset_folder, marked: markedCfg, source_dir, sanitize } = this.config; const { prependRoot, postAsset } = markedCfg; const { path, text } = data; @@ -258,8 +259,17 @@ module.exports = function(data, options) { } } - return marked(text, Object.assign({ + let sanitizer = function(html){ return html; }; + + if(sanitize) { + let param = {} + if(sanitize !== true){ + param = sanitize; + } + sanitizer = function(html){ return domPurify.sanitize(html, param); }; + } + return sanitizer(marked(text, Object.assign({ renderer, tokenizer - }, markedCfg, options, { postPath })); + }, markedCfg, options, { postPath }))); }; diff --git a/package.json b/package.json index 78e5871..275b502 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,9 @@ ], "license": "MIT", "dependencies": { - "hexo-util": "^2.1.0", - "marked": "^2.0.0" + "hexo-util": "^2.5.0", + "marked": "^2.1.3", + "dompurify": "^2.3.0" }, "devDependencies": { "chai": "^4.2.0", From 33b08845a5e87ba480116dad4e2c44c000a359e3 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 6 Aug 2021 20:22:15 +0200 Subject: [PATCH 2/6] fix --- README.md | 10 +++++----- index.js | 2 +- lib/renderer.js | 23 +++++++++++++---------- package.json | 3 ++- test/index.js | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 54 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 79e9f6f..01f9205 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ marked: autolink: true mangle: true sanitizeUrl: false - sanitize; false, + dompurify: false, headerIds: true lazyload: false prependRoot: false @@ -62,7 +62,7 @@ marked: - **mangle** - Escape autolinked email address with HTML character references. * This is to obscure email address from _basic_ crawler used by spam bot, while still readable to web browsers. - **sanitizeUrl** - Remove URLs that start with `javascript:`, `vbscript:` and `data:`. -- **sanitize** - Enable [DOMPurify](https://github.com/cure53/DOMPurify) to be run on the rendered Markdown. See below for configuration +- **dompurify** - Enable [DOMPurify](https://github.com/cure53/DOMPurify) to be run on the rendered Markdown. See below for configuration - **headerIds** - Insert header id, e.g. `

text

`. Useful for inserting anchor link to each paragraph with a heading. - **anchorAlias** - Enables custom header id * Example: `## [foo](#bar)`, id will be set as "bar". @@ -97,16 +97,16 @@ For more options, see [Marked](https://marked.js.org/using_advanced#options). Du [DOMPurify](https://github.com/cure53/DOMPurify) can be enabled to sanitize the rendered HTML. -To enable it, pass an object containing the DomPurify options: +To enable it, pass an object containing the DOMPurify options: ```json -sanitize: true +dompurify: true ``` Or you can enable specific DOMPurify options (but according to DOMPurify authors, the default options are safe): ```yml -sanitize: +dompurify: FORBID_TAGS: - "style" ``` diff --git a/index.js b/index.js index 32966cc..3781d18 100644 --- a/index.js +++ b/index.js @@ -14,7 +14,7 @@ hexo.config.marked = Object.assign({ autolink: true, mangle: true, sanitizeUrl: false, - sanitize: false, + dompurify: false, headerIds: true, anchorAlias: false, lazyload: false, diff --git a/lib/renderer.js b/lib/renderer.js index 595708d..a37fa47 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -1,7 +1,8 @@ 'use strict'; const marked = require('marked'); -const domPurify = require('dompurify'); +const createDOMPurify = require('dompurify'); +const { JSDOM } = require('jsdom'); const { escape } = require('marked/src/helpers'); const { encodeURL, slugize, stripHTML, url_for, isExternalLink } = require('hexo-util'); const MarkedRenderer = marked.Renderer; @@ -236,8 +237,8 @@ class Tokenizer extends MarkedTokenizer { } module.exports = function(data, options) { - const { post_asset_folder, marked: markedCfg, source_dir, sanitize } = this.config; - const { prependRoot, postAsset } = markedCfg; + const { post_asset_folder, marked: markedCfg, source_dir } = this.config; + const { prependRoot, postAsset, dompurify } = markedCfg; const { path, text } = data; // exec filter to extend renderer. @@ -259,14 +260,16 @@ module.exports = function(data, options) { } } - let sanitizer = function(html){ return html; }; - - if(sanitize) { - let param = {} - if(sanitize !== true){ - param = sanitize; + let sanitizer = function(html) { return html; }; + + if (dompurify) { + const window = new JSDOM('').window; + const DOMPurify = createDOMPurify(window); + let param = {}; + if (dompurify !== true) { + param = dompurify; } - sanitizer = function(html){ return domPurify.sanitize(html, param); }; + sanitizer = function(html) { return DOMPurify.sanitize(html, param); }; } return sanitizer(marked(text, Object.assign({ renderer, diff --git a/package.json b/package.json index 275b502..2357660 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "dependencies": { "hexo-util": "^2.5.0", "marked": "^2.1.3", - "dompurify": "^2.3.0" + "dompurify": "^2.3.0", + "jsdom": "^16.7.0" }, "devDependencies": { "chai": "^4.2.0", diff --git a/test/index.js b/test/index.js index 123fc21..1205b0a 100644 --- a/test/index.js +++ b/test/index.js @@ -949,4 +949,37 @@ describe('Marked renderer', () => { result.content.should.eql('

foo {% lorem %}

\n'); }); }); + + describe('sanitize HTML with DOMPurify', () => { + const body = [ + '**safe markdown**', + '', + 'unsafe link', + '', + '[Hexo](http://hexo.io)' + ].join('\n'); + + it('sanitize enabled, default options', () => { + hexo.config.marked.dompurify = true; + const result = r({text: body}); + + result.should.eql([ + '

safe markdown

\n', + '

unsafe link

\n', + '

Hexo

\n' + ].join('')); + }); + + it('sanitize enabled, with options', () => { + hexo.config.marked.dompurify = { FORBID_TAGS: ['strong'] }; + const result = r({text: body}); + + result.should.eql([ + '

safe markdown

\n', + '

unsafe link

\n', + '

Hexo

\n' + ].join('')); + }); + + }); }); From 22bc7a266f6344eadb9dfab8b1e4811835335177 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 7 Aug 2021 22:19:44 +0200 Subject: [PATCH 3/6] Update lib/renderer.js Co-authored-by: Sukka --- lib/renderer.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/renderer.js b/lib/renderer.js index a37fa47..faedd7a 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -1,8 +1,13 @@ 'use strict'; const marked = require('marked'); -const createDOMPurify = require('dompurify'); -const { JSDOM } = require('jsdom'); +let JSDOM; +let createDOMPurify; + +if (config.dompurify) { + createDOMPurify = require('dompurify'); + JSDOM = require('jsdom').JSDOM; +} const { escape } = require('marked/src/helpers'); const { encodeURL, slugize, stripHTML, url_for, isExternalLink } = require('hexo-util'); const MarkedRenderer = marked.Renderer; From 64964d3abfc020a09ce9cf83160006a06f3b421b Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 10 Aug 2021 13:55:09 +0200 Subject: [PATCH 4/6] fix --- lib/renderer.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/renderer.js b/lib/renderer.js index faedd7a..cbba892 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -1,13 +1,10 @@ 'use strict'; const marked = require('marked'); + let JSDOM; let createDOMPurify; -if (config.dompurify) { - createDOMPurify = require('dompurify'); - JSDOM = require('jsdom').JSDOM; -} const { escape } = require('marked/src/helpers'); const { encodeURL, slugize, stripHTML, url_for, isExternalLink } = require('hexo-util'); const MarkedRenderer = marked.Renderer; @@ -268,6 +265,10 @@ module.exports = function(data, options) { let sanitizer = function(html) { return html; }; if (dompurify) { + if (createDOMPurify == undefined && JSDOM == undefined) { + createDOMPurify = require('dompurify'); + JSDOM = require('jsdom').JSDOM; + } const window = new JSDOM('').window; const DOMPurify = createDOMPurify(window); let param = {}; From dbfe57ced58ada25163e22b8183987d770f6c04b Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 10 Aug 2021 13:56:17 +0200 Subject: [PATCH 5/6] fix --- lib/renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/renderer.js b/lib/renderer.js index cbba892..14d46a6 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -265,7 +265,7 @@ module.exports = function(data, options) { let sanitizer = function(html) { return html; }; if (dompurify) { - if (createDOMPurify == undefined && JSDOM == undefined) { + if (createDOMPurify === undefined && JSDOM === undefined) { createDOMPurify = require('dompurify'); JSDOM = require('jsdom').JSDOM; } From 5fe195b8643770049f426c2b98ba5e59d05eb2c4 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 10 Aug 2021 13:57:54 +0200 Subject: [PATCH 6/6] fix --- lib/renderer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/renderer.js b/lib/renderer.js index 14d46a6..e31b91a 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -2,8 +2,8 @@ const marked = require('marked'); -let JSDOM; -let createDOMPurify; +let JSDOM, + createDOMPurify; const { escape } = require('marked/src/helpers'); const { encodeURL, slugize, stripHTML, url_for, isExternalLink } = require('hexo-util');