diff --git a/bower.json b/bower.json index 393db02ad1..5f72f6ff04 100644 --- a/bower.json +++ b/bower.json @@ -9,6 +9,7 @@ "create-react-class": "https://cdn.jsdelivr.net/npm/create-react-class@15.6.3/create-react-class.min.js", "es6-promise": "~1.0", "font-awesome": "components/font-awesome#~4.7.0", + "google-caja": "5669", "jed": "~1.1.1", "jquery": "components/jquery#~3.5.0", "jquery-typeahead": "~2.10.6", diff --git a/notebook/static/base/js/security.js b/notebook/static/base/js/security.js index 7d01321ce8..bc79dedd31 100644 --- a/notebook/static/base/js/security.js +++ b/notebook/static/base/js/security.js @@ -3,24 +3,124 @@ define([ 'jquery', - 'components/sanitizer/index', -], function($, sanitizer) { + 'components/google-caja/html-css-sanitizer-minified', +], function($, sanitize) { "use strict"; - + var noop = function (x) { return x; }; - var defaultSanitizer = sanitizer.defaultSanitizer; - + + var caja; + if (window && window.html) { + caja = window.html; + caja.html4 = window.html4; + caja.sanitizeStylesheet = window.sanitizeStylesheet; + } + + var sanitizeAttribs = function (tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) { + /** + * add trusting data-attributes to the default sanitizeAttribs from caja + * this function is mostly copied from the caja source + */ + var ATTRIBS = caja.html4.ATTRIBS; + for (var i = 0; i < attribs.length; i += 2) { + var attribName = attribs[i]; + if (attribName.substr(0,5) == 'data-') { + var attribKey = '*::' + attribName; + if (!ATTRIBS.hasOwnProperty(attribKey)) { + ATTRIBS[attribKey] = 0; + } + } + } + // Caja doesn't allow data uri for img::src, see + // https://github.com/google/caja/issues/1558 + // This is not a security issue for browser post ie6 though, so we + // disable the check + // https://www.owasp.org/index.php/Script_in_IMG_tags + ATTRIBS['img::src'] = 0; + return caja.sanitizeAttribs(tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger); + }; + + var sanitize_css = function (css, tagPolicy) { + /** + * sanitize CSS + * like sanitize_html, but for CSS + * called by sanitize_stylesheets + */ + return caja.sanitizeStylesheet( + window.location.pathname, + css, + { + containerClass: null, + idSuffix: '', + tagPolicy: tagPolicy, + virtualizeAttrName: noop + }, + noop + ); + }; + + var sanitize_stylesheets = function (html, tagPolicy) { + /** + * sanitize just the css in style tags in a block of html + * called by sanitize_html, if allow_css is true + */ + var h = $("
").append(html); + var style_tags = h.find("style"); + if (!style_tags.length) { + // no style tags to sanitize + return html; + } + style_tags.each(function(i, style) { + style.innerHTML = sanitize_css(style.innerHTML, tagPolicy); + }); + return h.html(); + }; + var sanitize_html = function (html, allow_css) { /** * sanitize HTML * if allow_css is true (default: false), CSS is sanitized as well. * otherwise, CSS elements and attributes are simply removed. */ - const options = {}; - if (!allow_css) { - options.allowedStyles = {}; - } - return defaultSanitizer.sanitize(html, options); + var html4 = caja.html4; + + if (allow_css) { + // allow sanitization of style tags, + // not just scrubbing + html4.ELEMENTS.style &= ~html4.eflags.UNSAFE; + html4.ATTRIBS.style = html4.atype.STYLE; + } else { + // scrub all CSS + html4.ELEMENTS.style |= html4.eflags.UNSAFE; + html4.ATTRIBS.style = html4.atype.SCRIPT; + } + + var record_messages = function (msg, opts) { + console.log("HTML Sanitizer", msg, opts); + }; + + var policy = function (tagName, attribs) { + if (!(html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) { + return { + 'attribs': sanitizeAttribs(tagName, attribs, + noop, noop, record_messages) + }; + } else { + record_messages(tagName + " removed", { + change: "removed", + tagName: tagName + }); + } + }; + + var sanitized = caja.sanitizeWithPolicy(html, policy); + + if (allow_css) { + // sanitize style tags as stylesheets + sanitized = sanitize_stylesheets(sanitized, policy); + } + + return sanitized; }; var sanitize_html_and_parse = function (html, allow_css) { @@ -41,11 +141,12 @@ define([ $.htmlPrefilter = prev_htmlPrefilter; // Set it back again } }; - + var security = { + caja: caja, sanitize_html_and_parse: sanitize_html_and_parse, sanitize_html: sanitize_html }; return security; -}); +}); \ No newline at end of file diff --git a/setupbase.py b/setupbase.py index 8849a85e3b..271dfaa74f 100644 --- a/setupbase.py +++ b/setupbase.py @@ -135,6 +135,7 @@ def find_package_data(): pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"), pjoin(components, "create-react-class", "index.js"), pjoin(components, "font-awesome", "css", "*.css"), + pjoin(components, "google-caja", "html-css-sanitizer-minified.js"), pjoin(components, "es6-promise", "*.js"), pjoin(components, "font-awesome", "fonts", "*.*"), pjoin(components, "jed", "jed.js"),