@@ -0,0 +1,76 @@
1+"""Playwright tests for markdown-svg-renderer.html."""
2+3+import pathlib
4+5+from playwright.sync_api import Page, expect
6+7+8+test_dir = pathlib.Path(__file__).parent.absolute()
9+root = test_dir.parent.absolute()
10+11+12+def test_svg_is_rendered_raw_in_a_network_isolated_iframe(
13+page: Page, unused_port_server
14+):
15+unused_port_server.start(root)
16+leaked_urls = []
17+18+def record_leak(route):
19+leaked_urls.append(route.request.url)
20+route.abort()
21+22+page.route("**/svg-leak-probe-*", record_leak)
23+page.goto(
24+f"http://127.0.0.1:{unused_port_server.port}/markdown-svg-renderer.html"
25+ )
26+27+svg = """<svg viewBox="0 0 120 80" xmlns="http://www.w3.org/2000/svg">
28+ <style>.wheel { fill: #22c55e; }</style>
29+ <defs><circle id="wheel" class="wheel" r="20"/></defs>
30+ <use href="#wheel" x="30" y="40"/>
31+ <use href="#wheel" x="90" y="40"/>
32+ <script>
33+ document.documentElement.setAttribute("data-script-ran", "yes");
34+ fetch("http://127.0.0.1:%s/svg-leak-probe-script");
35+ </script>
36+ <image href="http://127.0.0.1:%s/svg-leak-probe-image"/>
37+ <foreignObject width="10" height="10">
38+ <iframe xmlns="http://www.w3.org/1999/xhtml"
39+ src="http://127.0.0.1:%s/svg-leak-probe-frame"></iframe>
40+ </foreignObject>
41+</svg>""" % ((unused_port_server.port,) * 3)
42+43+page.locator("#input").fill(f"```svg\n{svg}\n```")
44+block = page.locator("svg-block")
45+expect(block).to_be_visible()
46+47+# The SVG is not passed through an allowlist, so valid SVG features such
48+# as style and use survive intact.
49+assert block.get_attribute("data-svg") == svg + "\n"
50+51+iframe_locator = page.locator("svg-block iframe")
52+assert iframe_locator.get_attribute("sandbox") == ""
53+csp = iframe_locator.get_attribute("csp")
54+assert csp is not None
55+assert "default-src 'none'" in csp
56+assert "script-src 'none'" in csp
57+assert "style-src 'unsafe-inline'" in csp
58+assert "img-src data: blob:" in csp
59+60+srcdoc = iframe_locator.get_attribute("srcdoc")
61+assert srcdoc is not None
62+assert srcdoc.startswith(
63+'<!doctype html>\n<meta http-equiv="Content-Security-Policy"'
64+ )
65+66+iframe_element = iframe_locator.element_handle()
67+assert iframe_element is not None
68+iframe = iframe_element.content_frame()
69+assert iframe is not None
70+expect(iframe.locator("use")).to_have_count(2)
71+expect(iframe.locator("circle")).to_have_css("fill", "rgb(34, 197, 94)")
72+73+page.wait_for_timeout(500)
74+assert iframe.locator("svg").get_attribute("data-script-ran") is None
75+assert leaked_urls == []
76+