diff --git a/superset/utils/core.py b/superset/utils/core.py index 23a3017bf2c..6ef548b771f 100644 --- a/superset/utils/core.py +++ b/superset/utils/core.py @@ -488,6 +488,7 @@ def error_msg_from_exception(ex: Exception) -> str: def markdown(raw: str, markup_wrap: bool | None = False) -> str: + """Render Markdown to sanitized HTML.""" safe_markdown_tags = { "h1", "h2", @@ -517,7 +518,7 @@ def markdown(raw: str, markup_wrap: bool | None = False) -> str: } safe_markdown_attrs = { "img": {"src", "alt", "title"}, - "a": {"href", "alt", "title"}, + "a": {"href", "alt", "title", "target"}, } safe = md.markdown( raw or "", @@ -528,6 +529,7 @@ def markdown(raw: str, markup_wrap: bool | None = False) -> str: ], ) # pylint: disable=no-member + # nh3 preserves supported link attributes and enforces a safe rel value. safe = nh3.clean(safe, tags=safe_markdown_tags, attributes=safe_markdown_attrs) if markup_wrap: safe = Markup(safe) diff --git a/tests/unit_tests/utils/test_core.py b/tests/unit_tests/utils/test_core.py index 463ee32180a..5e663dd095a 100644 --- a/tests/unit_tests/utils/test_core.py +++ b/tests/unit_tests/utils/test_core.py @@ -39,6 +39,7 @@ from superset.utils.core import ( get_stacktrace, get_user_agent, is_test, + markdown, merge_extra_filters, merge_extra_form_data, merge_request_params, @@ -1688,3 +1689,44 @@ def test_sanitize_url_blocks_dangerous(): """Test that dangerous URL schemes are blocked.""" assert sanitize_url("javascript:alert('xss')") == "" assert sanitize_url("data:text/html,") == "" + + +def test_markdown_basic() -> None: + result = markdown("**bold**") + + assert "bold" in result + + +def test_markdown_allows_target_blank_on_links() -> None: + raw = 'Click here' + result = markdown(raw) + + assert 'href="https://example.com"' in result + assert 'target="_blank"' in result + assert 'rel="noopener noreferrer"' in result + + +def test_markdown_replaces_custom_rel_with_safe_rel() -> None: + raw = 'Click' + result = markdown(raw) + + assert 'href="https://example.com"' in result + assert ">Click" in result + assert 'rel="noopener noreferrer"' in result + assert 'rel="external"' not in result + + +def test_markdown_sanitizes_dangerous_content() -> None: + raw = '
Content
' + result = markdown(raw) + + assert "