diff --git a/django/contrib/auth/password_validation.py b/django/contrib/auth/password_validation.py
index 1350dec9c8..4c26e88ac8 100644
--- a/django/contrib/auth/password_validation.py
+++ b/django/contrib/auth/password_validation.py
@@ -9,7 +9,7 @@ from django.core.exceptions import (
FieldDoesNotExist, ImproperlyConfigured, ValidationError,
)
from django.utils.functional import lazy
-from django.utils.html import format_html
+from django.utils.html import format_html, format_html_join
from django.utils.module_loading import import_string
from django.utils.translation import gettext as _, ngettext
@@ -81,8 +81,8 @@ def _password_validators_help_text_html(password_validators=None):
in an
.
"""
help_texts = password_validators_help_texts(password_validators)
- help_items = [format_html('- {}
', help_text) for help_text in help_texts]
- return '' % ''.join(help_items) if help_items else ''
+ help_items = format_html_join('', '- {}
', ((help_text,) for help_text in help_texts))
+ return format_html('', help_items) if help_items else ''
password_validators_help_text_html = lazy(_password_validators_help_text_html, str)
diff --git a/tests/auth_tests/test_validators.py b/tests/auth_tests/test_validators.py
index 068dec9981..d43efc6a3c 100644
--- a/tests/auth_tests/test_validators.py
+++ b/tests/auth_tests/test_validators.py
@@ -13,6 +13,7 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.test import TestCase, override_settings
from django.test.utils import isolate_apps
+from django.utils.html import conditional_escape
@override_settings(AUTH_PASSWORD_VALIDATORS=[
@@ -68,6 +69,15 @@ class PasswordValidationTest(TestCase):
self.assertEqual(help_text.count('- '), 2)
self.assertIn('12 characters', help_text)
+ def test_password_validators_help_text_html_escaping(self):
+ class AmpersandValidator:
+ def get_help_text(self):
+ return 'Must contain &'
+ help_text = password_validators_help_text_html([AmpersandValidator()])
+ self.assertEqual(help_text, '')
+ # help_text is marked safe and therefore unchanged by conditional_escape().
+ self.assertEqual(help_text, conditional_escape(help_text))
+
@override_settings(AUTH_PASSWORD_VALIDATORS=[])
def test_empty_password_validator_help_text_html(self):
self.assertEqual(password_validators_help_text_html(), '')