diff --git a/datasette/tracer.py b/datasette/tracer.py index 772f0405..62c3c90c 100644 --- a/datasette/tracer.py +++ b/datasette/tracer.py @@ -1,5 +1,6 @@ import asyncio from contextlib import contextmanager +from markupsafe import escape import time import json import traceback @@ -123,7 +124,7 @@ class AsgiTracer: except IndexError: content_type = "" if "text/html" in content_type and b"" in accumulated_body: - extra = json.dumps(trace_info, indent=2) + extra = escape(json.dumps(trace_info, indent=2)) extra_html = f"
{extra}
".encode("utf8") accumulated_body = accumulated_body.replace(b"", extra_html) elif "json" in content_type and accumulated_body.startswith(b"{"): diff --git a/datasette/version.py b/datasette/version.py index 4dcf73b0..aa3bef15 100644 --- a/datasette/version.py +++ b/datasette/version.py @@ -1,2 +1,2 @@ -__version__ = "0.56" +__version__ = "0.56.1" __version_info__ = tuple(__version__.split(".")) diff --git a/docs/changelog.rst b/docs/changelog.rst index 756badce..a4888404 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,14 @@ Changelog ========= +.. _v0_56_1: + +0.56.1 (2021-06-05) +------------------- + +.. warning:: + This release fixes a `reflected cross-site scripting `__ security hole with the ``?_trace=1`` feature. You should upgrade to this version, or to Datasette 0.57, as soon as possible. (:issue:`1360`) + .. _v0_56: 0.56 (2021-03-28) diff --git a/tests/test_html.py b/tests/test_html.py index 9e86ebc2..d15e7e57 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -1612,3 +1612,9 @@ def test_navigation_menu_links( assert ( details.find("a", {"href": link}) is None ), f"{link} found but should not have been in nav menu" + + +def test_trace_correctly_escaped(app_client): + response = app_client.get("/fixtures?sql=select+'

Hello'&_trace=1") + assert "select '

Hello" not in response.text + assert "select '<h1>Hello" in response.text