diff --git a/testit-adapter-behave/setup.py b/testit-adapter-behave/setup.py
index da4572ed..1a4f9697 100644
--- a/testit-adapter-behave/setup.py
+++ b/testit-adapter-behave/setup.py
@@ -1,6 +1,6 @@
from setuptools import find_packages, setup
-VERSION = "5.2.0"
+VERSION = "5.2.1"
setup(
name='testit-adapter-behave',
diff --git a/testit-adapter-nose/setup.py b/testit-adapter-nose/setup.py
index 728c82ab..2bddff7a 100644
--- a/testit-adapter-nose/setup.py
+++ b/testit-adapter-nose/setup.py
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
-VERSION = "5.2.0"
+VERSION = "5.2.1"
setup(
name='testit-adapter-nose',
diff --git a/testit-adapter-pytest/setup.py b/testit-adapter-pytest/setup.py
index afc0a97d..eaf4b274 100644
--- a/testit-adapter-pytest/setup.py
+++ b/testit-adapter-pytest/setup.py
@@ -1,6 +1,6 @@
from setuptools import find_packages, setup
-VERSION = "5.2.0"
+VERSION = "5.2.1"
setup(
name='testit-adapter-pytest',
diff --git a/testit-adapter-pytest/tests/test_xss_parametrize_escape.py b/testit-adapter-pytest/tests/test_xss_parametrize_escape.py
new file mode 100644
index 00000000..86256c3c
--- /dev/null
+++ b/testit-adapter-pytest/tests/test_xss_parametrize_escape.py
@@ -0,0 +1,39 @@
+"""Ensure pytest parametrize XSS values are escaped in TestResult / API payload."""
+
+from testit_adapter_pytest.models.executable_test import ExecutableTest
+from testit_adapter_pytest.utils import convert_executable_test_to_test_result_model
+from testit_python_commons.client.converter import Converter
+from adapters_api import ApiClient
+
+
+def test_parametrize_script_payload_escaped_in_parameters_and_external_key():
+ payload = ''
+ executable = ExecutableTest(
+ external_id="xss_ext",
+ name=f"with xss {payload}",
+ duration=1,
+ parameters={"description": payload},
+ properties={},
+ namespace="tests",
+ classname=None,
+ title=None,
+ description=None,
+ links=[],
+ labels=[],
+ tags=[],
+ work_item_ids=[],
+ node_id=f"tests/test_xss_parametrize.py::test_05_with_chg_xss[{payload}]",
+ outcome="Passed",
+ status_type="Succeeded",
+ )
+
+ test_result = convert_executable_test_to_test_result_model(executable)
+ assert test_result.get_parameters()["description"] == '<script>alert("1")</script>'
+ assert ""),
+ SampleData("Second", "
"),
+ ]
+ result = HtmlEscapeUtils.escape_html_in_object(test_list)
+
+ self.assertEqual(result[0].description, "<script>alert(1)</script>")
+ self.assertEqual(result[1].description, "<img src=x onerror=alert(1)>")
+
+ def test_escape_html_in_list_of_strings(self):
+ tags = ["", "safe", "x"]
+ result = HtmlEscapeUtils.escape_html_in_object(tags)
+
+ self.assertEqual(result[0], "<script>alert(1)</script>")
+ self.assertEqual(result[1], "safe")
+ self.assertEqual(result[2], "<b>x</b>")
+
+ def test_escape_html_in_list_of_dicts(self):
+ labels = [{"name": ""}, {"name": "ok"}]
+ result = HtmlEscapeUtils.escape_html_in_object(labels)
+
+ self.assertEqual(result[0]["name"], "<script>alert(1)</script>")
+ self.assertEqual(result[1]["name"], "ok")
+
def test_escape_disabled_by_env_var(self):
"""Test that escaping can be disabled via environment variable"""
os.environ[HtmlEscapeUtils.NO_ESCAPE_HTML_ENV_VAR] = "true"
@@ -131,6 +157,25 @@ def test_escape_html_in_dictionary(self):
self.assertEqual(result["tags"][0], "<tag>")
self.assertEqual(result["tags"][1], "normal_tag")
+ def test_escape_openapi_model_parameters(self):
+ """OpenAPI models hide fields from dir(); parameters must still be escaped."""
+ from adapters_api.models import AutoTestResultsForTestRunModel, TestStatusType
+
+ model = AutoTestResultsForTestRunModel(
+ configuration_id="cfg",
+ auto_test_external_id="ext",
+ status_type=TestStatusType("Succeeded"),
+ parameters={"description": ''},
+ message='
',
+ )
+ HtmlEscapeUtils.escape_html_in_object(model)
+
+ self.assertEqual(
+ model.parameters["description"],
+ '<script>alert("1")</script>',
+ )
+ self.assertEqual(model.message, "<img src=x onerror=alert(1)>")
+
if __name__ == '__main__':
unittest.main()
diff --git a/testit-python-commons/tests/test_xss_parameters_escape.py b/testit-python-commons/tests/test_xss_parameters_escape.py
new file mode 100644
index 00000000..921a568c
--- /dev/null
+++ b/testit-python-commons/tests/test_xss_parameters_escape.py
@@ -0,0 +1,72 @@
+"""Regression: XSS in pytest params must be escaped before TMS API payloads."""
+
+import importlib.util
+import unittest
+from pathlib import Path
+
+from adapters_api import ApiClient
+from testit_python_commons.client.converter import Converter
+from testit_python_commons.models.test_result import TestResult
+from testit_python_commons.utils.html_escape_utils import HtmlEscapeUtils
+
+
+def _load_xss_payloads():
+ path = Path(__file__).resolve().parents[2] / "test_files" / "test_xss_parametrize.py"
+ spec = importlib.util.spec_from_file_location("test_xss_parametrize", path)
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module.XSS_PAYLOADS
+
+
+class TestXssParametersEscape(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.payloads = _load_xss_payloads()
+
+ def test_test_result_parameters_escaped_for_all_payloads(self):
+ for payload in self.payloads:
+ with self.subTest(payload=payload):
+ result = TestResult().set_parameters({"description": payload})
+ value = result.get_parameters()["description"]
+ if HtmlEscapeUtils._HTML_TAG_PATTERN.search(payload):
+ self.assertNotIn("'
+ # Simulate model built WITHOUT going through TestResult setters (raw params)
+ class FakeResult:
+ def __init__(self):
+ self.parameters = {"description": payload}
+ self.message = payload
+
+ models = [FakeResult(), FakeResult()]
+ HtmlEscapeUtils.escape_html_in_object(models)
+ for model in models:
+ self.assertEqual(
+ model.parameters["description"],
+ '<script>alert("1")</script>',
+ )
+ self.assertEqual(model.message, '<script>alert("1")</script>')
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/update_versions.sh b/update_versions.sh
index d79a943f..32217d4c 100644
--- a/update_versions.sh
+++ b/update_versions.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-NEW_VERSION="5.2.0"
+NEW_VERSION="5.2.1"
echo "Updating all adapters to version: $NEW_VERSION"