Skip to content

Commit 7a50495

Browse files
committed
fix: remove duplicate values from CSP directives
1 parent de02b76 commit 7a50495

File tree

3 files changed

+47
-13
lines changed

3 files changed

+47
-13
lines changed

csp/tests/test_decorators.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def view_with_decorator(request: HttpRequest) -> HttpResponseBase:
6363
mw.process_response(request, response)
6464
assert HEADER_REPORT_ONLY not in response.headers
6565
policy_list = sorted(response[HEADER].split("; "))
66-
assert policy_list == ["default-src 'self'", f"img-src foo.com bar.com 'nonce-{getattr(request, 'csp_nonce')}'"]
66+
assert policy_list == ["default-src 'self'", f"img-src 'nonce-{getattr(request, 'csp_nonce')}' bar.com foo.com"]
6767

6868
response = view_without_decorator(request)
6969
mw.process_response(request, response)
@@ -96,7 +96,7 @@ def view_with_decorator(request: HttpRequest) -> HttpResponseBase:
9696
mw.process_response(request, response)
9797
assert HEADER not in response.headers
9898
policy_list = sorted(response[HEADER_REPORT_ONLY].split("; "))
99-
assert policy_list == ["default-src 'self'", f"img-src foo.com bar.com 'nonce-{getattr(request, 'csp_nonce')}'"]
99+
assert policy_list == ["default-src 'self'", f"img-src 'nonce-{getattr(request, 'csp_nonce')}' bar.com foo.com"]
100100

101101
response = view_without_decorator(request)
102102
mw.process_response(request, response)

csp/tests/test_utils.py

Lines changed: 33 additions & 3 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()
@@ -300,7 +301,7 @@ def test_require_trusted_types_for() -> None:
300301
def test_trusted_types() -> None:
301302
policy = build_policy()
302303
policy_eq(
303-
"default-src 'self'; trusted-types strictPolicy laxPolicy default 'allow-duplicates'",
304+
"default-src 'self'; trusted-types 'allow-duplicates' default laxPolicy strictPolicy",
304305
policy,
305306
)
306307

@@ -319,14 +320,14 @@ def test_block_all_mixed_content() -> None:
319320

320321
def test_nonce() -> None:
321322
policy = build_policy(nonce="abc123")
322-
policy_eq("default-src 'self' 'nonce-abc123'", policy)
323+
policy_eq("default-src 'nonce-abc123' 'self'", policy)
323324

324325

325326
@override_settings(CONTENT_SECURITY_POLICY={"DIRECTIVES": {"default-src": [SELF], "script-src": [SELF, NONCE], "style-src": [SELF, NONCE]}})
326327
def test_nonce_in_value() -> None:
327328
policy = build_policy(nonce="abc123")
328329
policy_eq(
329-
"default-src 'self'; script-src 'self' 'nonce-abc123'; style-src 'self' 'nonce-abc123'",
330+
"script-src 'nonce-abc123' 'self'; default-src 'self'; style-src 'nonce-abc123' 'self'",
330331
policy,
331332
)
332333

@@ -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 = sorted(set(value)) # Deduplicate and sort value
133137
policy_parts[key] = " ".join(value)
134138

135139
if report_uri:

0 commit comments

Comments
 (0)