diff --git a/wagtail/contrib/modeladmin/forms.py b/wagtail/contrib/modeladmin/forms.py
index 92feab81f0..be5431a56f 100644
--- a/wagtail/contrib/modeladmin/forms.py
+++ b/wagtail/contrib/modeladmin/forms.py
@@ -1,5 +1,4 @@
from django import forms
-from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from wagtail.models import Page
@@ -12,7 +11,7 @@ class PageChoiceField(forms.ModelChoiceField):
obj.get_ancestors(inclusive=True).exclude(depth=1).specific(defer=True)
):
bits.append(ancestor.get_admin_display_title())
- return mark_safe(''.join(bits))
+ return " | ".join(bits)
class ParentChooserForm(forms.Form):
diff --git a/wagtail/contrib/modeladmin/tests/test_page_modeladmin.py b/wagtail/contrib/modeladmin/tests/test_page_modeladmin.py
index e09f2f4ab7..8c589cadd8 100644
--- a/wagtail/contrib/modeladmin/tests/test_page_modeladmin.py
+++ b/wagtail/contrib/modeladmin/tests/test_page_modeladmin.py
@@ -306,6 +306,21 @@ class TestChooseParentView(WagtailTestUtils, TestCase):
"""
self.assertContains(response, expected, html=True)
+ def test_page_title_html_escaping(self):
+ homepage = Page.objects.get(url_path="/home/")
+ business_index = BusinessIndex(
+ title="Title with ",
+ )
+ homepage.add_child(instance=business_index)
+
+ response = self.client.get("/admin/tests/businesschild/choose_parent/")
+
+ self.assertEqual(response.status_code, 200)
+ self.assertNotContains(response, "Title with ")
+ self.assertContains(
+ response, "Title with <script>alert('XSS')</script>"
+ )
+
class TestChooseParentViewForNonSuperuser(WagtailTestUtils, TestCase):
fixtures = ["test_specific.json"]