diff --git a/Lib/profiling/sampling/stack_collector.py b/Lib/profiling/sampling/stack_collector.py
index 60061b4a2ecf11..0c754c3598b57c 100644
--- a/Lib/profiling/sampling/stack_collector.py
+++ b/Lib/profiling/sampling/stack_collector.py
@@ -505,6 +505,10 @@ def _create_flamegraph_html(self, data):
# Python recursion limit. iterencode() uses the Python encoder.
data_json = "".join(json.JSONEncoder().iterencode(data))
+ # Prevent profile strings from closing the script element or starting
+ # an HTML comment, while preserving their values in JavaScript.
+ data_json = data_json.replace("<", "\\u003c")
+
template_dir = importlib.resources.files(__package__)
vendor_dir = template_dir / "_vendor"
assets_dir = template_dir / "_assets"
diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
index d4ba2df5044559..a8c99fc5b29a13 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
@@ -7,6 +7,7 @@
import sys
import tempfile
import unittest
+from unittest import mock
from test.support import is_emscripten, run_with_limited_c_stack, set_recursion_limit
@@ -596,6 +597,35 @@ def test_flamegraph_collector_export(self):
self.assertIn('"sample_interval_usec": 10000', content)
self.assertIn("samples * data.stats.sample_interval_usec / 1000", content)
+ def test_flamegraph_html_escapes_script_data(self):
+ collector = FlamegraphCollector(1000)
+ data = {
+ "strings": [
+ "",
+ "",
+ "