diff --git a/src/django_nh3/apps.py b/src/django_nh3/apps.py index e3fdc86..3cc7366 100644 --- a/src/django_nh3/apps.py +++ b/src/django_nh3/apps.py @@ -6,3 +6,6 @@ class DjangoNh3AppConfig(AppConfig): name = "django_nh3" verbose_name = "django-nh3" + + def ready(self) -> None: + from . import checks # noqa: F401 diff --git a/src/django_nh3/checks.py b/src/django_nh3/checks.py new file mode 100644 index 0000000..ee09b59 --- /dev/null +++ b/src/django_nh3/checks.py @@ -0,0 +1,38 @@ +from typing import Any + +from django.apps import AppConfig +from django.conf import settings +from django.core.checks import CheckMessage, Tags, Warning, register + + +@register(Tags.security) +def check_nh3_settings( + app_configs: list[AppConfig] | None, **kwargs: Any +) -> list[CheckMessage]: + """ + Inspects the NH3_ALLOWED_ATTRIBUTES setting to ensure that the 'style' + attribute is not allowed, as nh3 does not sanitize CSS content. + """ + errors: list[CheckMessage] = [] + allowed_attributes = getattr(settings, "NH3_ALLOWED_ATTRIBUTES", {}) + + found_style = False + if isinstance(allowed_attributes, dict): + for _tag, attrs in allowed_attributes.items(): + if "style" in attrs: + found_style = True + break + + if found_style: + errors.append( + Warning( + "The 'style' attribute is allowed in NH3_ALLOWED_ATTRIBUTES.", + hint=( + "Allowing 'style' poses a security risk (XSS) because nh3 " + "does not sanitize CSS content. Ensure you strictly trust " + "the input if this attribute is required." + ), + id="django_nh3.W001", + ) + ) + return errors diff --git a/tests/test_checks.py b/tests/test_checks.py new file mode 100644 index 0000000..0b9c136 --- /dev/null +++ b/tests/test_checks.py @@ -0,0 +1,28 @@ +from django.test import override_settings + +from django_nh3.checks import check_nh3_settings + + +@override_settings(NH3_ALLOWED_ATTRIBUTES={"div": {"style"}}) +def test_check_warns_about_style(): + errors = check_nh3_settings(None) + assert len(errors) == 1 + assert errors[0].id == "django_nh3.W001" + + +@override_settings(NH3_ALLOWED_ATTRIBUTES={"div": {"class", "id"}}) +def test_check_is_silent_when_safe(): + errors = check_nh3_settings(None) + assert len(errors) == 0 + + +@override_settings(NH3_ALLOWED_ATTRIBUTES={}) +def test_check_handles_empty_dict(): + errors = check_nh3_settings(None) + assert len(errors) == 0 + + +@override_settings(NH3_ALLOWED_ATTRIBUTES=None) +def test_check_handles_non_dict_attributes(): + errors = check_nh3_settings(None) + assert len(errors) == 0