##// END OF EJS Templates
fix(jupiter): sanitizing rendering (HTML) of jupyter notebooks, upgraded bleach version. Fixes RCCE-15, RCCE-14
ilin.s -
r5252:1ecdda64 default
parent child Browse files
Show More
@@ -121,7 +121,7 b' mysqlclient==2.1.1'
121 121 nbconvert==7.7.3
122 122 beautifulsoup4==4.11.2
123 123 soupsieve==2.4
124 bleach==6.0.0
124 bleach==6.1.0
125 125 six==1.16.0
126 126 webencodings==0.5.1
127 127 defusedxml==0.7.1
@@ -424,11 +424,13 b' class MarkupRenderer(object):'
424 424 @classmethod
425 425 def jupyter(cls, source, safe=True):
426 426 from rhodecode.lib import helpers
427 from .html_sanitizer_defs import markdown_attrs, all_tags, all_styles
427 428
428 429 from traitlets import default, config
429 430 import nbformat
430 431 from nbconvert import HTMLExporter
431 432 from nbconvert.preprocessors import Preprocessor
433 from nbconvert.preprocessors.sanitize import SanitizeHTML
432 434
433 435 class CustomHTMLExporter(HTMLExporter):
434 436
@@ -439,24 +441,20 b' class MarkupRenderer(object):'
439 441
440 442 class Sandbox(Preprocessor):
441 443
442 def preprocess(self, nb, resources):
444 def preprocess_cell(self, cell, resources, cell_index):
445 if not safe:
446 return cell, resources
443 447 sandbox_text = 'SandBoxed(IPython.core.display.Javascript object)'
444 for cell in nb['cells']:
445 if not safe:
446 continue
448 if cell.cell_type == "markdown":
449 cell.source = cls.sanitize_html(cell.source)
450 return cell, resources
447 451
448 if 'outputs' in cell:
449 for cell_output in cell['outputs']:
450 if 'data' in cell_output:
451 if 'application/javascript' in cell_output['data']:
452 cell_output['data']['text/plain'] = sandbox_text
453 cell_output['data'].pop('application/javascript', None)
454
455 if 'source' in cell and cell['cell_type'] == 'markdown':
456 # sanitize similar like in markdown
457 cell['source'] = cls.sanitize_html(cell['source'])
458
459 return nb, resources
452 for cell_output in cell.outputs:
453 if 'data' in cell_output:
454 if 'application/javascript' in cell_output['data']:
455 cell_output['data']['text/plain'] = sandbox_text
456 cell_output['data'].pop('application/javascript', None)
457 return cell, resources
460 458
461 459 def _sanitize_resources(input_resources):
462 460 """
@@ -475,8 +473,29 b' class MarkupRenderer(object):'
475 473
476 474 def as_html(notebook):
477 475 conf = config.Config()
478 conf.CustomHTMLExporter.default_preprocessors = [Sandbox]
476 # TODO: Keep an eye on the order of preprocessors
477 conf.CustomHTMLExporter.default_preprocessors = [Sandbox, SanitizeHTML]
479 478 conf.Sandbox.enabled = True
479 conf.SanitizeHTML.enabled = True
480 conf.SanitizeHTML.attributes = markdown_attrs
481 conf.SanitizeHTML.tags = all_tags
482 conf.SanitizeHTML.styles = all_styles
483 conf.SanitizeHTML.sanitized_output_types = {
484 "text/html",
485 "text/markdown",
486 }
487 conf.SanitizeHTML.safe_output_keys = {
488 "metadata",
489 "text/plain",
490 "text/latex",
491 "application/json",
492 "image/png",
493 "image/jpg"
494 "image/jpeg",
495 "image/svg",
496 "image/svg+xml"
497 }
498
480 499 html_exporter = CustomHTMLExporter(config=conf)
481 500
482 501 (body, resources) = html_exporter.from_notebook_node(notebook)
@@ -17,6 +17,7 b''
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 import mock
20 21 import pytest
21 22
22 23 from rhodecode.lib.markup_renderer import (
@@ -778,8 +779,119 b' def test_relative_links(src_html, expect'
778 779 """, "Hello, World!")
779 780 ])
780 781 def test_jp_notebook_html_generation(notebook_source, expected_output):
781 import mock
782 782 with mock.patch('rhodecode.lib.helpers.asset'):
783 783 body = MarkupRenderer.jupyter(notebook_source)
784 784 assert "<!-- ## IPYTHON NOTEBOOK RENDERING ## -->" in body
785 785 assert expected_output in body
786
787
788 @pytest.mark.parametrize("notebook_source, expected_output", [
789 ({"cells": [
790 {
791 "cell_type": "code",
792 "execution_count": 0,
793 "metadata": {},
794 "outputs": [
795 {
796 "data": {
797 "text/html": [
798 "<select><iframe></select><img src=x: onerror=alert('xss')>\n"
799 ],
800 "text/plain": []
801 },
802 "metadata": {},
803 "output_type": "display_data"
804 }
805 ],
806 "source": [
807 ""
808 ]
809 }
810 ],
811 "metadata": {
812 "kernelspec": {
813 "display_name": "Python 3",
814 "language": "python",
815 "name": "python3"
816 },
817 "language_info": {
818 "codemirror_mode": {
819 "name": "ipython",
820 "version": 3
821 },
822 "file_extension": ".py",
823 "mimetype": "text/x-python",
824 "name": "python",
825 "nbconvert_exporter": "python",
826 "pygments_lexer": "ipython3",
827 "version": "3.9.6"
828 }
829 },
830 "nbformat": 4,
831 "nbformat_minor": 5
832 }, '<select></select><img alt="No description has been provided for this image"/>'),
833 ({
834 "cells": [
835 {
836 "cell_type": "markdown",
837 "metadata": {},
838 "source": [
839 "<label for=buttonid style=\"cursor: text\">not safe to click here</label>\n"
840 ]
841 },
842 {
843 "cell_type": "markdown",
844 "metadata": {
845 "highlighter": "codemirror"
846 },
847 "source": "<div class=\"jp-InputArea-editor\"><div class=\"CodeMirror cm-s-jupyter\"><div class=\"CodeMirror-scroll\"><div class=\"CodeMirror-sizer\"><div style=\"top:0px; position:relative\"><div class=\"CodeMirror-lines\"><div style=\"outline:none; position:relative\"><div class=\"CodeMirror-code\"><div style=\"position: relative\"><label for=buttonid style=\"cursor: text\"><pre class=\"CodeMirror-line\" style=\"background:transparent\"><span style=\"padding-right: 0.1px\"><span class=\"cm-builtin\">print</span>(<span class=\"cm-string\">&quot;Also not safe to click here&quot;</span>)</span></pre></label></div></div></div></div></div></div></div></div></div>"
848 },
849 {
850 "cell_type": "code",
851 "execution_count": 0,
852 "metadata": {
853 "xrender": True
854 },
855 "outputs": [
856 {
857 "data": {
858 "text/html": [
859 "<form id=jp-mk-edit action='javascript:alert(1)' style='display:none'><button type=submit id=buttonid></form>\n"
860 ],
861 "text/plain": []
862 },
863 "metadata": {},
864 "output_type": "display_data"
865 }
866 ],
867 "source": ""
868 }
869 ],
870 "metadata": {
871 "kernelspec": {
872 "display_name": "Python 3",
873 "language": "python",
874 "name": "python3"
875 },
876 "language_info": {
877 "codemirror_mode": {
878 "name": "ipython",
879 "version": 3
880 },
881 "file_extension": ".py",
882 "mimetype": "text/x-python",
883 "name": "python",
884 "nbconvert_exporter": "python",
885 "pygments_lexer": "ipython3",
886 "version": "3.9.6"
887 }
888 },
889 "nbformat": 4,
890 "nbformat_minor": 5
891 }, '<form style="display:none;">')
892 ])
893 def test_jp_notebook_against_xss(notebook_source, expected_output):
894 import json
895 with mock.patch('rhodecode.lib.helpers.asset'):
896 body = MarkupRenderer.jupyter(json.dumps(notebook_source))
897 assert expected_output in body
General Comments 0
You need to be logged in to leave comments. Login now