Skip to content

Commit 55e3ea0

Browse files
committed
fix: remove duplicate values from CSP directives
1 parent 0d5e54b commit 55e3ea0

File tree

3 files changed

+43
-9
lines changed

3 files changed

+43
-9
lines changed

csp/tests/test_templatetags.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def test_async_attribute_with_falsey(self) -> None:
3939
{% script src="foo.com/bar.js" async=False %}
4040
{% endscript %}"""
4141

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

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

csp/tests/test_utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def test_default_src() -> None:
5555
policy = build_policy()
5656
policy_eq("default-src example.com example2.com", policy)
5757

58+
5859
@override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {"default-src": {"example.com", "example2.com"}}})
5960
def test_default_src_is_set() -> None:
6061
policy = build_policy()
@@ -337,6 +338,35 @@ def test_only_nonce_in_value() -> None:
337338
policy_eq("default-src 'nonce-abc123'", policy)
338339

339340

341+
@override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {"img-src": ["example.com", "example.com"]}})
342+
def test_deduplicate_values() -> None:
343+
"""
344+
GitHub issue #40 - given project settings as a tuple, and
345+
an update/replace with a string, concatenate correctly.
346+
"""
347+
policy = build_policy()
348+
policy_eq("default-src 'self'; img-src example.com", policy)
349+
350+
351+
@override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {"img-src": ["example.com", "example.com"]}})
352+
def test_deduplicate_values_update() -> None:
353+
"""
354+
GitHub issue #40 - given project settings as a tuple, and
355+
an update/replace with a string, concatenate correctly.
356+
"""
357+
policy = build_policy(update={"img-src": "example.com"})
358+
policy_eq("default-src 'self'; img-src example.com", policy)
359+
360+
361+
@override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {"img-src": ("example.com",)}})
362+
def test_deduplicate_values_replace() -> None:
363+
"""
364+
Demonstrate that GitHub issue #40 doesn't affect replacements
365+
"""
366+
policy = build_policy(replace={"img-src": ["example2.com", "example2.com"]})
367+
policy_eq("default-src 'self'; img-src example2.com", policy)
368+
369+
340370
def test_boolean_directives() -> None:
341371
for directive in ["upgrade-insecure-requests", "block-all-mixed-content"]:
342372
with override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {directive: True}}):

csp/utils.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,18 @@ def build_policy(
9898
v = config[k]
9999
if v is not None:
100100
v = copy.copy(v)
101-
if not isinstance(v, (list, tuple, set)):
101+
if isinstance(v, set):
102+
v = sorted(v)
103+
if not isinstance(v, (list, tuple)):
102104
v = (v,)
103105
csp[k] = v
104106

105107
for k, v in update.items():
106108
if v is not None:
107-
if not isinstance(v, (list, tuple, set)):
109+
v = copy.copy(v)
110+
if isinstance(v, set):
111+
v = sorted(v)
112+
if not isinstance(v, (list, tuple)):
108113
v = (v,)
109114
if csp.get(k) is None:
110115
csp[k] = v
@@ -117,19 +122,18 @@ def build_policy(
117122

118123
for key, value in csp.items():
119124
# Check for boolean directives.
120-
if len(value) == 1:
121-
val = list(value)[0]
122-
if isinstance(val, bool):
123-
if value[0] is True:
124-
policy_parts[key] = ""
125-
continue
125+
if len(value) == 1 and isinstance(value[0], bool):
126+
if value[0] is True:
127+
policy_parts[key] = ""
128+
continue
126129
if NONCE in value:
127130
if nonce:
128131
value = [f"'nonce-{nonce}'" if v == NONCE else v for v in value]
129132
else:
130133
# Strip the `NONCE` sentinel value if no nonce is provided.
131134
value = [v for v in value if v != NONCE]
132135

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

135139
if report_uri:

0 commit comments

Comments
 (0)