From f89daf2e62a749402de1bb0a994fc1cbaea2f140 Mon Sep 17 00:00:00 2001
From: Steven Chand
Date: Thu, 20 Aug 2026 17:20:05 -0700
Subject: [PATCH 1/6] [SC-17911] Send log_text Markdown without script tags
---
tests/test_api_client.py | 58 +++++++++++++++++++++++++++++++++++++---
validmind/api_client.py | 56 +++++++++++++++++++++++++++++++-------
2 files changed, 100 insertions(+), 14 deletions(-)
diff --git a/tests/test_api_client.py b/tests/test_api_client.py
index 971dbce2e..122202b54 100644
--- a/tests/test_api_client.py
+++ b/tests/test_api_client.py
@@ -315,9 +315,8 @@ def test_log_text_generates_text_and_logs_metadata(
data=json.dumps(
{
"content_id": "dataset_summary_text",
- "text": md_to_html(
- "## Generated Summary\nGenerated content.", mathml=True
- ),
+ "text": "## Generated Summary\nGenerated content.",
+ "text_format": "markdown",
}
),
)
@@ -366,7 +365,58 @@ def test_log_text_logs_metadata_with_section_id(
data=json.dumps(
{
"content_id": "dataset_summary_text",
- "text": md_to_html("Generated content.", mathml=True),
+ "text": "Generated content.",
+ "text_format": "markdown",
+ }
+ ),
+ )
+
+ @patch("aiohttp.ClientSession.post")
+ def test_log_text_sends_tex_as_waf_safe_markdown(self, mock_post: MagicMock):
+ equation = r"$WOE = \ln\dfrac{\%\ of\ Events}{\%\ of\ Non-Events}$"
+ mock_post.return_value = MockAsyncResponse(
+ 200,
+ json={
+ "content_id": "text_woe_equation",
+ "text": '',
+ },
+ )
+
+ self.run_async(
+ api_client.alog_text,
+ "text_woe_equation",
+ text=equation,
+ )
+
+ mock_post.assert_called_once_with(
+ f"{os.environ['VM_API_HOST']}/log_metadata",
+ data=json.dumps(
+ {
+ "content_id": "text_woe_equation",
+ "text": equation,
+ "text_format": "markdown",
+ }
+ ),
+ )
+ request_body = mock_post.call_args.kwargs["data"]
+ self.assertNotIn("
',\n",
+ " },\n",
+ "]\n",
+ "\n",
+ "assert {is_html(case[\"text\"]) for case in CASES} == {False, True}\n",
+ "pd.DataFrame(\n",
+ " {\n",
+ " \"case\": [case[\"name\"] for case in CASES],\n",
+ " \"detected_as\": [\"html\" if is_html(case[\"text\"]) else \"markdown\" for case in CASES],\n",
+ " }\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "capture-helper",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "async def capture_log_text_request(case, supports_markdown):\n",
+ " \"\"\"Run alog_text while capturing the serialized log_metadata body.\"\"\"\n",
+ " captured = {}\n",
+ "\n",
+ " async def capture_post(path, params=None, data=None, **kwargs):\n",
+ " captured[\"path\"] = path\n",
+ " captured[\"params\"] = params\n",
+ " captured[\"body\"] = json.loads(data)\n",
+ " return {\"content_id\": captured[\"body\"][\"content_id\"], \"text\": captured[\"body\"].get(\"text\")}\n",
+ "\n",
+ " original_flags = client_config.feature_flags\n",
+ " try:\n",
+ " client_config.feature_flags = {\"log_metadata_markdown\": supports_markdown}\n",
+ " with patch.object(api_client, \"_post\", new=capture_post):\n",
+ " await api_client.alog_text(\n",
+ " content_id=f'test_description:sc17911_{case[\"name\"]}',\n",
+ " text=case[\"text\"],\n",
+ " )\n",
+ " finally:\n",
+ " client_config.feature_flags = original_flags\n",
+ "\n",
+ " assert captured[\"path\"] == \"log_metadata\"\n",
+ " return captured[\"body\"]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "new-backend-heading",
+ "metadata": {},
+ "source": [
+ "## New backend: WAF-safe Markdown transport\n",
+ "\n",
+ "Markdown and TeX must stay raw in the request. Fully formed HTML must retain the legacy pass-through behavior."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "new-backend-matrix",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "new_backend_rows = []\n",
+ "for case in CASES:\n",
+ " body = await capture_log_text_request(case, supports_markdown=True)\n",
+ " html_input = is_html(case[\"text\"])\n",
+ "\n",
+ " assert body[\"text\"] == case[\"text\"]\n",
+ " if html_input:\n",
+ " assert \"text_format\" not in body\n",
+ " else:\n",
+ " assert body[\"text_format\"] == \"markdown\"\n",
+ " assert \"