diff --git a/CHANGES.md b/CHANGES.md index c6d07459bc..9ffa791fee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ Parser: - add first rough performance testing script (#3280) [Austin Schick][] +- add `throwUnescapedHTML` to warn against potential HTML injection [Josh Goebel][] - expose `regex` helper functions via `hljs` injection [Josh Goebel][] - concat - lookahead diff --git a/docs/api.rst b/docs/api.rst index a5d7880fe9..f4301a2889 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -89,6 +89,9 @@ Configures global options: * ``languageDetectRe``: a regex to configure how CSS class names map to language (allows class names like say `color-as-php` vs the default of `language-php`, etc.) * ``noHighlightRe``: a regex to configure which CSS classes are to be skipped completely. * ``cssSelector``: a CSS selector to configure which elements are affected by ``hljs.highlightAll``. Defaults to ``'pre code'``. +* ``ignoreUnescapedHTML``: do not log warnings to console about unescaped HTML in code blocks +* ``throwUnescapedHTML``: throw a ``HTMLInjectionError`` when ``highlightElement`` is asked to highlight content that includes unescaped HTML + Accepts an object representing options with the values to updated. Other options don't change :: diff --git a/src/highlight.js b/src/highlight.js index 5049329b48..6f546e6ccc 100644 --- a/src/highlight.js +++ b/src/highlight.js @@ -13,6 +13,7 @@ import * as MODES from './lib/modes.js'; import { compileLanguage } from './lib/mode_compiler.js'; import * as packageJSON from '../package.json'; import * as logger from "./lib/logger.js"; +import HTMLInjectionError from "./lib/html_injection_error.js"; /** @typedef {import('highlight.js').Mode} Mode @@ -66,6 +67,7 @@ const HLJS = function(hljs) { /** @type HLJSOptions */ let options = { ignoreUnescapedHTML: false, + throwUnescapedHTML: false, noHighlightRe: /^(no-?highlight)$/i, languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, classPrefix: 'hljs-', @@ -724,11 +726,24 @@ const HLJS = function(hljs) { fire("before:highlightElement", { el: element, language: language }); - // we should be all text, no child nodes - if (!options.ignoreUnescapedHTML && element.children.length > 0) { - console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."); - console.warn("https://github.com/highlightjs/highlight.js/issues/2886"); - console.warn(element); + // we should be all text, no child nodes (unescaped HTML) - this is possibly + // an HTML injection attack - it's likely too late if this is already in + // production (the code has likely already done its damage by the time + // we're seeing it)... but we yell loudly about this so that hopefully it's + // more likely to be caught in development before making it to production + if (element.children.length > 0) { + if (!options.ignoreUnescapedHTML) { + console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."); + console.warn("https://github.com/highlightjs/highlight.js/issues/2886"); + console.warn(element); + } + if (options.throwUnescapedHTML) { + const err = new HTMLInjectionError( + "One of your code blocks includes unescaped HTML.", + element.innerHTML + ); + throw err; + } } node = element; diff --git a/src/lib/html_injection_error.js b/src/lib/html_injection_error.js new file mode 100644 index 0000000000..332a0c13bf --- /dev/null +++ b/src/lib/html_injection_error.js @@ -0,0 +1,7 @@ +export default class HTMLInjectionError extends Error { + constructor(reason, html) { + super(reason); + this.name = "HTMLInjectionError"; + this.html = html; + } +} diff --git a/types/index.d.ts b/types/index.d.ts index c7f3eb5ed4..dae5dff32e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -134,6 +134,7 @@ declare module 'highlight.js' { languages?: string[] __emitter: EmitterConstructor ignoreUnescapedHTML?: boolean + throwUnescapedHTML?: boolean } export interface CallbackResponse {