diff --git a/backend/utils/input_sanitizer.py b/backend/utils/input_sanitizer.py
index 36772727e6..8ac4b305eb 100644
--- a/backend/utils/input_sanitizer.py
+++ b/backend/utils/input_sanitizer.py
@@ -1,7 +1,11 @@
+import logging
import re
+from typing import NoReturn
from rest_framework.serializers import ValidationError
+logger = logging.getLogger(__name__)
+
# Pattern to detect HTML/script tags (closed tags and unclosed tags starting with a letter)
# The second alternative catches unclosed tags like "", "description": ""})
+ assert not s.is_valid()
+ assert "name" in s.errors
+
+ def test_rejects_html_in_description(self):
+ s = PlainSerializer(data={"name": "ok", "description": "
"})
+ assert not s.is_valid()
+ assert "description" in s.errors
+
+ def test_clean_input_passes(self):
+ s = PlainSerializer(data={"name": "My Workflow", "description": "Plain text."})
+ assert s.is_valid(), s.errors
+
+ def test_does_not_touch_non_charfield(self):
+ s = PlainSerializer(data={"name": "ok", "description": "", "count": 42})
+ assert s.is_valid(), s.errors
+
+ def test_each_field_gets_its_own_validator(self):
+ """Default-arg closure capture: the field_name in the error must match the offender."""
+ s = PlainSerializer(data={"name": "ok", "description": ""})
+ assert not s.is_valid()
+ assert "description" in s.errors
+ msg = str(s.errors["description"][0])
+ assert "description" in msg.lower()
+
+ def test_html_safe_fields_opts_out(self):
+ s = WithOptOutSerializer(
+ data={"name": "ok", "prompt": "step 1"}
+ )
+ assert s.is_valid(), s.errors
+
+ def test_html_safe_fields_does_not_leak_to_other_fields(self):
+ s = WithOptOutSerializer(
+ data={"name": "