From e3c5773e8f3ada75548a672cd7f2760b55495d53 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 3 Sep 2026 10:44:49 +0100 Subject: [PATCH] Fix #014: sanitize themed text kinds + script-blocking CSP (stored XSS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit markdown/deck/html_fragment rendered author-influenced content verbatim, so \n\n\n\n[x](javascript:alert(1))", + ).data["id"] + html = self._stored_html(aid).lower() + self.assertNotIn("", html) + self.assertIn('class="codehilite"', html) + self.assertIn('def', html) + self.assertIn("/assets/ab12cd34ef56", html) # relative image URL survives + + def test_html_full_is_not_sanitized(self): + aid = self.create_artifact( + kind="html_full", + content="", + ).data["id"] + # html_full is the deliberate verbatim escape hatch — its script stays. + self.assertIn("", self._stored_html(aid)) + + def test_sanitized_kinds_served_with_script_blocking_csp(self): + aid = self.create_artifact(kind="markdown", content="# hi").data["id"] + self.client.force_login(self.user) + response = self.client.get(reverse("artifact", args=[aid])) + self.assertIn("script-src 'none'", response.headers.get("Content-Security-Policy", "")) + + def test_html_full_served_without_restrictive_csp(self): + aid = self.create_artifact( + kind="html_full", content="x" + ).data["id"] + self.client.force_login(self.user) + response = self.client.get(reverse("artifact", args=[aid])) + self.assertIsNone(response.headers.get("Content-Security-Policy")) +