Skip to content

Commit 8bc1477

Browse files
author
Rob Hudson
committed
Add constants for CSP keywords
This helps avoid potential errors introduced by incorrectly quoting CSP keywords.
1 parent 3413de3 commit 8bc1477

File tree

6 files changed

+106
-37
lines changed

6 files changed

+106
-37
lines changed

csp/constants.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
11
HEADER = "Content-Security-Policy"
22
HEADER_REPORT_ONLY = "Content-Security-Policy-Report-Only"
3+
4+
NONE = "'none'"
5+
REPORT_SAMPLE = "'report-sample'"
6+
SELF = "'self'"
7+
STRICT_DYNAMIC = "'strict-dynamic'"
8+
UNSAFE_ALLOW_REDIRECTS = "'unsafe-allow-redirects'"
9+
UNSAFE_EVAL = "'unsafe-eval'"
10+
UNSAFE_HASHES = "'unsafe-hashes'"
11+
UNSAFE_INLINE = "'unsafe-inline'"
12+
WASM_UNSAFE_EVAL = "'wasm-unsafe-eval'"

csp/tests/test_middleware.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.test import RequestFactory
77
from django.test.utils import override_settings
88

9-
from csp.constants import HEADER, HEADER_REPORT_ONLY
9+
from csp.constants import HEADER, HEADER_REPORT_ONLY, SELF
1010
from csp.middleware import CSPMiddleware
1111
from csp.tests.utils import response
1212

@@ -23,7 +23,7 @@ def test_add_header():
2323

2424
@override_settings(
2525
CONTENT_SECURITY_POLICY={"DIRECTIVES": {"default-src": ["example.com"]}},
26-
CONTENT_SECURITY_POLICY_REPORT_ONLY={"DIRECTIVES": {"default-src": ["'self'"]}},
26+
CONTENT_SECURITY_POLICY_REPORT_ONLY={"DIRECTIVES": {"default-src": [SELF]}},
2727
)
2828
def test_both_headers():
2929
request = rf.get("/")
@@ -51,7 +51,7 @@ def text_exclude():
5151

5252
@override_settings(
5353
CONTENT_SECURITY_POLICY=None,
54-
CONTENT_SECURITY_POLICY_REPORT_ONLY={"DIRECTIVES": {"default-src": ["'self'"]}},
54+
CONTENT_SECURITY_POLICY_REPORT_ONLY={"DIRECTIVES": {"default-src": [SELF]}},
5555
)
5656
def test_report_only():
5757
request = rf.get("/")

csp/tests/test_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from django.test.utils import override_settings
22
from django.utils.functional import lazy
33

4+
from csp.constants import NONE, SELF
45
from csp.utils import build_policy, default_config, DEFAULT_DIRECTIVES
56

67

@@ -182,7 +183,7 @@ def test_replace_missing_setting():
182183

183184

184185
def test_config():
185-
policy = build_policy(config={"default-src": ["'none'"], "img-src": ["'self'"]})
186+
policy = build_policy(config={"default-src": [NONE], "img-src": [SELF]})
186187
policy_eq("default-src 'none'; img-src 'self'", policy)
187188

188189

csp/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
from django.conf import settings
77
from django.utils.encoding import force_str
88

9+
from csp.constants import SELF
910

1011
DEFAULT_DIRECTIVES = {
1112
# Fetch Directives
1213
"child-src": None,
1314
"connect-src": None,
14-
"default-src": ["'self'"],
15+
"default-src": [SELF],
1516
"script-src": None,
1617
"script-src-attr": None,
1718
"script-src-elem": None,

docs/configuration.rst

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,54 @@ a more slightly strict policy and is used to test the policy without breaking th
4343

4444
.. code-block:: python
4545
46+
from csp.constants import NONE, SELF
47+
4648
CONTENT_SECURITY_POLICY = {
4749
"EXCLUDE_URL_PREFIXES": ["/excluded-path/"],
4850
"DIRECTIVES": {
49-
"default-src": ["'self'", "cdn.example.net"],
50-
"frame-ancestors": ["'self'"],
51-
"form-action": ["'self'"],
51+
"default-src": [SELF, "cdn.example.net"],
52+
"frame-ancestors": [SELF],
53+
"form-action": [SELF],
5254
"report-uri": "/csp-report/",
5355
},
5456
}
5557
5658
CONTENT_SECURITY_POLICY_REPORT_ONLY = {
5759
"EXCLUDE_URL_PREFIXES": ["/excluded-path/"],
5860
"DIRECTIVES": {
59-
"default-src": ["'none'"],
60-
"connect-src": ["'self'"],
61-
"img-src": ["'self'"],
62-
"form-action": ["'self'"],
63-
"frame-ancestors": ["'self'"],
64-
"script-src": ["'self'"],
65-
"style-src": ["'self'"],
61+
"default-src": [NONE],
62+
"connect-src": [SELF],
63+
"img-src": [SELF],
64+
"form-action": [SELF],
65+
"frame-ancestors": [SELF],
66+
"script-src": [SELF],
67+
"style-src": [SELF],
6668
"upgrade-insecure-requests": True,
6769
"report-uri": "/csp-report/",
6870
},
6971
}
7072
73+
.. note::
74+
75+
In the above example, the constant ``NONE`` is converted to the CSP keyword ``"'none'"`` and
76+
is distinct from Python's ``None`` value. The CSP keyword ``'none'`` is a special value that
77+
signifies that you do not want any sources for this directive. The ``None`` value is a
78+
Python keyword that represents the absence of a value and when used as the value of a directive,
79+
it will remove the directive from the policy.
80+
81+
This is useful when using the ``@csp_replace`` decorator to effectively clear a directive from
82+
the base configuration as defined in the settings. For example, if the Django settings the
83+
``frame-ancestors`` directive is set to a list of sources and you want to remove the
84+
``frame-ancestors`` directive from the policy for this view:
85+
86+
.. code-block:: python
87+
88+
from csp.decorators import csp_replace
89+
90+
91+
@csp_replace({"frame-ancestors": None})
92+
def my_view(request): ...
93+
7194
7295
Policy Settings
7396
===============
@@ -86,8 +109,6 @@ policy.
86109
Scripting flaw on, e.g., ``excluded-page/`` can therefore be leveraged to access everything
87110
on the same origin.
88111

89-
# TODO: I can't find any documentation on the above warning.
90-
91112
``REPORT_PERCENTAGE``
92113
Percentage of requests that should see the ``report-uri`` directive.
93114
Use this to throttle the number of CSP violation reports made to your
@@ -101,8 +122,11 @@ policy.
101122

102123
.. note::
103124
The "special" source values of ``'self'``, ``'unsafe-inline'``, ``'unsafe-eval'``,
104-
``'none'`` and hash-source (``'sha256-...'``) must be quoted!
105-
e.g.: ``"default-src": ["'self'"]``. Without quotes they will not work as intended.
125+
``'strict-dynamic'``, ``'none'``, etc. must be quoted! e.g.: ``"default-src": ["'self'"]``.
126+
Without quotes they will not work as intended.
127+
128+
Consider using the ``csp.constants`` module to get these values to help avoiding quoting
129+
errors or typos, e.g., ``from csp.constants import SELF, STRICT_DYNAMIC``.
106130

107131
.. note::
108132
Deprecated features of CSP in general have been moved to the bottom of this list.

docs/decorators.rst

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,35 @@ Modifying the Policy with Decorators
77
Content Security Policies should be restricted and paranoid by default. You may, on some views,
88
need to expand or change the policy. django-csp includes four decorators to help.
99

10+
All decorators take an optional keyword argument, ``REPORT_ONLY``, which defaults to ``False``. If
11+
set to ``True``, the decorator will update the report-only policy instead of the enforced policy.
1012

1113
``@csp_exempt``
1214
===============
1315

1416
Using the ``@csp_exempt`` decorator disables the CSP header on a given
1517
view.
1618

17-
::
19+
.. code-block:: python
1820
1921
from csp.decorators import csp_exempt
2022
23+
2124
# Will not have a CSP header.
2225
@csp_exempt()
2326
def myview(request):
2427
return render(...)
2528
29+
2630
# Will not have a CSP report-only header.
2731
@csp_exempt(REPORT_ONLY=True)
2832
def myview(request):
2933
return render(...)
3034
3135
You can manually set this on a per-response basis by setting the ``_csp_exempt``
32-
or ``_csp_exempt_ro`` attribute on the response to ``True``::
36+
or ``_csp_exempt_ro`` attribute on the response to ``True``:
37+
38+
.. code-block:: python
3339
3440
# Also will not have a CSP header.
3541
def myview(request):
@@ -55,15 +61,17 @@ decorator excpects a single dictionary argument, where the keys are the directiv
5561
are either strings, lists or tuples. An optional argument, ``REPORT_ONLY``, can be set to ``True``
5662
to update the report-only policy instead of the enforced policy.
5763

58-
::
64+
.. code-block:: python
5965
6066
from csp.decorators import csp_update
6167
68+
6269
# Will append imgsrv.com to the list of values for `img-src` in the enforced policy.
6370
@csp_update({"img-src": "imgsrv.com"})
6471
def myview(request):
6572
return render(...)
6673
74+
6775
# Will append cdn-img.com to the list of values for `img-src` in the report-only policy.
6876
@csp_update({"img-src": "cdn-img.com"}, REPORT_ONLY=True)
6977
def myview(request):
@@ -77,41 +85,66 @@ The ``@csp_replace`` decorator allows you to **replace** a source list specified
7785
there is no setting, the value passed to the decorator will be used verbatim. (See the note under
7886
``@csp_update``.) If the specified value is None, the corresponding key will not be included.
7987

80-
The arguments and values are the same as ``@csp_update``::
88+
The arguments and values are the same as ``@csp_update``:
89+
90+
.. code-block:: python
8191
8292
from csp.decorators import csp_replace
8393
94+
8495
# Will allow images only from imgsrv2.com in the enforced policy.
8596
@csp_replace({"img-src": "imgsrv2.com"})
8697
def myview(request):
8798
return render(...)
8899
100+
89101
# Will allow images only from cdn-img2.com in the report-only policy.
90102
@csp_replace({"img-src": "imgsrv2.com"})
91103
def myview(request):
92104
return render(...)
93105
106+
The ``csp_replace`` decorator can also be used to remove a directive from the policy by setting the
107+
value to ``None``. For example, if the ``frame-ancestors`` directive is set in the Django settings
108+
and you want to remove the ``frame-ancestors`` directive from the policy for this view:
109+
110+
.. code-block:: python
111+
112+
from csp.decorators import csp_replace
113+
114+
115+
@csp_replace({"frame-ancestors": None})
116+
def myview(request):
117+
return render(...)
118+
94119
95120
``@csp``
96121
========
97122

98123
If you need to set the entire policy on a view, ignoring all the settings, you can use the ``@csp``
99-
decorator. This, and the other decorators, can be stacked to update both policies if both are in
100-
use, as shown below. The arguments and values are as above::
124+
decorator. This can be stacked to update both the enforced policy and the report-only policy if both
125+
are in use, as shown below.
101126

127+
.. code-block:: python
128+
129+
from csp.constants import SELF, UNSAFE_INLINE
102130
from csp.decorators import csp
103131
104-
@csp({
105-
"default_src": ["'self'"],
106-
"img-src": ["imgsrv.com"],
107-
"script-src": ["scriptsrv.com", "googleanalytics.com", "'unsafe-inline'"]}
108-
})
109-
@csp({
110-
"default_src": ["'self'"],
111-
"img-src": ["imgsrv.com"],
112-
"script-src": ["scriptsrv.com", "googleanalytics.com"]},
113-
"frame-src": ["'self'"],
114-
REPORT_ONLY=True
115-
})
132+
133+
@csp(
134+
{
135+
"default_src": [SELF],
136+
"img-src": ["imgsrv.com"],
137+
"script-src": ["scriptsrv.com", "googleanalytics.com", UNSAFE_INLINE],
138+
}
139+
)
140+
@csp(
141+
{
142+
"default_src": [SELF],
143+
"img-src": ["imgsrv.com"],
144+
"script-src": ["scriptsrv.com", "googleanalytics.com"],
145+
"frame-src": [SELF],
146+
},
147+
REPORT_ONLY=True,
148+
)
116149
def myview(request):
117150
return render(...)

0 commit comments

Comments
 (0)