diff --git a/csp/policy.py b/csp/policy.py index 11567e5..2dc1c3d 100644 --- a/csp/policy.py +++ b/csp/policy.py @@ -36,7 +36,10 @@ def refresh_rules_cache() -> None: def _dedupe(values: list[str]) -> list[str]: - return list({CspRule.clean_value(v) for v in values}) + retval = {CspRule.clean_value(v) for v in values} + if "'none'" in retval and len(retval) > 1: + return list(retval - {"'none'"}) + return list(retval) def _downgrade(directive: str) -> str: diff --git a/tests/test_policy.py b/tests/test_policy.py index 6bcc8a3..dad0a08 100644 --- a/tests/test_policy.py +++ b/tests/test_policy.py @@ -4,7 +4,7 @@ from django.core.cache import cache from django.test import RequestFactory -from csp.policy import CACHE_KEY_RULES, _downgrade, format_as_csp, get_csp +from csp.policy import CACHE_KEY_RULES, _dedupe, _downgrade, format_as_csp, get_csp from csp.settings import CSP_REPORT_DIRECTIVE_DOWNGRADE @@ -38,3 +38,28 @@ def test__downgrade() -> None: assert CSP_REPORT_DIRECTIVE_DOWNGRADE["script-src-elem"] == "script-src" assert _downgrade("script-src-elem") == "script-src" assert _downgrade("made-up-directive") == "made-up-directive" + + +@pytest.mark.parametrize( + "input_list,output_list", + [ + ([], []), + (["'self'"], ["'self'"]), + (["'none'"], ["'none'"]), + (["'none'", "'self'"], ["'self'"]), + ], +) +def test__dedupe(input_list: list[str], output_list: list[str]) -> None: + """ + Test for console error when default-src is 'none' and has values. + + The Content-Security-Policy directive 'default-src' contains the + keyword 'none' alongside with other source expressions. The + keyword 'none' must be the only source expression in the + directive value, otherwise it is ignored. + + This same issue affects all directives that have 'none' as a value, + and so we fix it in the _dedupe function. + + """ + assert _dedupe(input_list) == output_list