Skip to content

Commit

Permalink
fix: remove duplicate values from CSP directives
Browse files Browse the repository at this point in the history
  • Loading branch information
crgwbr authored and jwhitlock committed Jan 15, 2025
1 parent 0e3c661 commit 7a8a44d
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 9 deletions.
2 changes: 1 addition & 1 deletion csp/tests/test_templatetags.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def test_async_attribute_with_falsey(self) -> None:
{% script src="foo.com/bar.js" async=False %}
{% endscript %}"""

expected = '<script nonce="{}" src="foo.com/bar.js" async=false>' "</script>"
expected = '<script nonce="{}" src="foo.com/bar.js" async=false></script>'

self.assert_template_eq(*self.process_templates(tpl, expected))

Expand Down
30 changes: 30 additions & 0 deletions csp/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def test_default_src() -> None:
policy = build_policy()
policy_eq("default-src example.com example2.com", policy)


@override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {"default-src": {"example.com", "example2.com"}}})
def test_default_src_is_set() -> None:
policy = build_policy()
Expand Down Expand Up @@ -337,6 +338,35 @@ def test_only_nonce_in_value() -> None:
policy_eq("default-src 'nonce-abc123'", policy)


@override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {"img-src": ["example.com", "example.com"]}})
def test_deduplicate_values() -> None:
"""
GitHub issue #40 - given project settings as a tuple, and
an update/replace with a string, concatenate correctly.
"""
policy = build_policy()
policy_eq("default-src 'self'; img-src example.com", policy)


@override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {"img-src": ["example.com", "example.com"]}})
def test_deduplicate_values_update() -> None:
"""
GitHub issue #40 - given project settings as a tuple, and
an update/replace with a string, concatenate correctly.
"""
policy = build_policy(update={"img-src": "example.com"})
policy_eq("default-src 'self'; img-src example.com", policy)


@override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {"img-src": ("example.com",)}})
def test_deduplicate_values_replace() -> None:
"""
Demonstrate that GitHub issue #40 doesn't affect replacements
"""
policy = build_policy(replace={"img-src": ["example2.com", "example2.com"]})
policy_eq("default-src 'self'; img-src example2.com", policy)


def test_boolean_directives() -> None:
for directive in ["upgrade-insecure-requests", "block-all-mixed-content"]:
with override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {directive: True}}):
Expand Down
20 changes: 12 additions & 8 deletions csp/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,18 @@ def build_policy(
v = config[k]
if v is not None:
v = copy.copy(v)
if not isinstance(v, (list, tuple, set)):
if isinstance(v, set):
v = sorted(v)
if not isinstance(v, (list, tuple)):
v = (v,)
csp[k] = v

for k, v in update.items():
if v is not None:
if not isinstance(v, (list, tuple, set)):
v = copy.copy(v)
if isinstance(v, set):
v = sorted(v)
if not isinstance(v, (list, tuple)):
v = (v,)
if csp.get(k) is None:
csp[k] = v
Expand All @@ -117,19 +122,18 @@ def build_policy(

for key, value in csp.items():
# Check for boolean directives.
if len(value) == 1:
val = list(value)[0]
if isinstance(val, bool):
if value[0] is True:
policy_parts[key] = ""
continue
if len(value) == 1 and isinstance(value[0], bool):
if value[0] is True:
policy_parts[key] = ""
continue
if NONCE in value:
if nonce:
value = [f"'nonce-{nonce}'" if v == NONCE else v for v in value]
else:
# Strip the `NONCE` sentinel value if no nonce is provided.
value = [v for v in value if v != NONCE]

value = list(dict.fromkeys(value)) # Deduplicate
policy_parts[key] = " ".join(value)

if report_uri:
Expand Down

0 comments on commit 7a8a44d

Please sign in to comment.