From 2580306db954f79e568d28b2948aea8a918267b1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 30 Aug 2019 14:40:11 -0400 Subject: [PATCH] feat: html sanitization on all filter:parse.* hooks, closes #7872 --- src/plugins/index.js | 28 ++++++++++++++++++++++++++++ src/posts/parse.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/plugins/index.js b/src/plugins/index.js index ee5dd30f90..c3c322d429 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -9,6 +9,7 @@ const nconf = require('nconf'); const util = require('util'); const user = require('../user'); +const posts = require('../posts'); const readdirAsync = util.promisify(fs.readdir); @@ -124,6 +125,33 @@ Plugins.reload = async function () { console.log(''); } + Plugins.registerHook('core', { + hook: 'filter:parse.post', + method: async (data) => { + data.postData.content = posts.sanitize(data.postData.content); + return data; + }, + }); + + Plugins.registerHook('core', { + hook: 'filter:parse.raw', + method: async content => posts.sanitize(content), + }); + + Plugins.registerHook('core', { + hook: 'filter:parse.aboutme', + method: async content => posts.sanitize(content), + }); + + Plugins.registerHook('core', { + hook: 'filter:parse.signature', + method: async (data) => { + data.userData.signature = posts.sanitize(data.userData.signature); + return data; + }, + }); + + // Lower priority runs earlier Object.keys(Plugins.loadedHooks).forEach(function (hook) { Plugins.loadedHooks[hook].sort((a, b) => a.priority - b.priority); }); diff --git a/src/posts/parse.js b/src/posts/parse.js index a90b511136..652287bd6a 100644 --- a/src/posts/parse.js +++ b/src/posts/parse.js @@ -3,12 +3,48 @@ var nconf = require('nconf'); var url = require('url'); var winston = require('winston'); +const sanitize = require('sanitize-html'); +const _ = require('lodash'); var meta = require('../meta'); var plugins = require('../plugins'); var translator = require('../translator'); var utils = require('../utils'); +let sanitizeConfig = { + allowedTags: sanitize.defaults.allowedTags.concat([ + // Some safe-to-use tags to add + 'span', 'a', 'pre', 'small', + 'sup', 'sub', 'u', 'del', + 'video', 'audio', 'iframe', 'embed', + 'img', 'tfoot', 'h1', 'h2', + 's', 'button', 'i', + ]), + allowedAttributes: { + ...sanitize.defaults.allowedAttributes, + a: ['href', 'hreflang', 'media', 'rel', 'target', 'type'], + img: ['alt', 'height', 'ismap', 'src', 'usemap', 'width', 'srcset'], + iframe: ['height', 'name', 'src', 'width'], + video: ['autoplay', 'controls', 'height', 'loop', 'muted', 'poster', 'preload', 'src', 'width'], + audio: ['autoplay', 'controls', 'loop', 'muted', 'preload', 'src'], + embed: ['height', 'src', 'type', 'width'], + }, + globalAttributes: ['accesskey', 'class', 'contenteditable', 'dir', + 'draggable', 'dropzone', 'hidden', 'id', 'lang', 'spellcheck', 'style', + 'tabindex', 'title', 'translate', 'aria-expanded', 'data-*', + ], +}; + +process.nextTick(async () => { + // Each allowed tags should have some common global attributes... + sanitizeConfig.allowedTags.forEach((tag) => { + sanitizeConfig.allowedAttributes[tag] = _.union(sanitizeConfig.allowedAttributes[tag], sanitizeConfig.globalAttributes); + }); + + // Some plugins might need to adjust or whitelist their own tags... + sanitizeConfig = await plugins.fireHook('filter:sanitize.config', sanitizeConfig); +}); + module.exports = function (Posts) { Posts.urlRegex = { regex: /href="([^"]+)"/g, @@ -77,6 +113,12 @@ module.exports = function (Posts) { return content; }; + Posts.sanitize = function (content) { + return sanitize(content, { + allowedTags: sanitizeConfig.allowedTags, allowedAttributes: sanitizeConfig.allowedAttributes, + }); + }; + function sanitizeSignature(signature) { signature = translator.escape(signature); var tagsToStrip = [];