Professional Documents
Culture Documents
GSoC 2023 Proposal - Bring CORS and CSP Into Core
GSoC 2023 Proposal - Bring CORS and CSP Into Core
Table of Contents
Table of Contents 1
Project Spec 1
CORS 2
CSP 4
Implementation Details 7
CORS 7
CORS Settings 7
CORS Middleware 8
CORS Decorators 8
CSP 9
CSP Middleware 9
CSP Settings 10
CSP Decorators 10
Timeline 11
References 12
About Me 13
Project Spec
Since there already exist two mature and popular libraries providing CORS and CSP
support for Django - django-cors-headers and django-csp - my proposed
implementation is to bring features from the two libraries into Django core, with the design
and API matching Django’s CSRF protection design and API.
The key goals of the proposal are covered in the headings below.
CORS
Similar to Django’s CSRF protection, we will provide the following features for CORS
protection -
1. CorsMiddleware for adding CORS headers to responses. This will do the heavy
lifting of actually adding headers to responses.
2. CORS decorators -
a. cors_protect - A decorator that will allow users to configure all CORS
settings at the same time. Using this decorator will only send the CORS
header passed to it as arguments, and other headers configured in the
settings will be ignored.
b. cors_update - A decorator that will allow users to configure all CORS
settings at the same time. Using the decorator will send the CORS headers
passed to it as arguments, as well as any additional CORS headers defined
in the settings.
c. cors_exempt - A decorator that will exempt a view from CORS protection.
3. CORS settings for configuring a global policy or good defaults -
a. CORS_ALLOWED_ORIGINS - Globally sets the allowed origins to a sequence
of strings and/or regular expressions.
b. CORS_ALLOW_ALL_ORIGINS - Globally sets the allowed origins to *
c. CORS_PATHS - A list/sequence of regexes that will define the urlpatterns
that will be applied CORS protection. Only responses for URLs that match
these regexes will be sent CORS headers.
d. CORS_ALLOWED_METHODS - Globally sets the allowed methods for CORS
requests
e. CORS_ALLOWED_HEADERS - Globally sets the allowed headers for CORS
requests
f. CORS_EXPOSE_HEADERS - Globally sets the headers that will be exposed
to the browser for CORS responses
g. CORS_PREFLIGHT_MAX_AGE - Globally sets the max age for caching a
preflight response
h. CORS_ALLOW_CREDENTIALS - Globally sets if credentials are allowed for
CORS requests
The implementation details for the middleware, decorators, and settings are covered in the
Implementation Details heading.
This means users will be able to configure CORS protection in a few ways. If they add
CorsMiddleware to the list of middlewares and configure the CORS_ALLOWED_ORIGINS
setting or set the CORS_ALLOW_ALL_ORIGINS setting to True, CORS protection will be
enabled project-wide and CORS headers will be sent for all requests.
If users add CorsMiddleware to the list of middlewares and configure the CORS_PATHS
setting in addition to CORS_ALLOWED_ORIGINS or CORS_ALLOW_ALL_ORIGINS, CORS
headers will be sent to all requests to URLs matching the CORS_PATHS setting.
This would be the bare minimum configuration needed to start sending CORS headers,
and CORS protection behavior can be customized by also configuring settings (d) through
(f).
Users can use the cors_protect and cors_update decorators to customize the CORS
headers sent on a per-view basis. Both cors_protect and cors_update will also take a
callable as an optional argument, which will allow users to customize the CORS headers
sent on a per-request basis. The callable will be passed the request, and should return a
dictionary specifying the CORS headers to be sent as keys (in lowercase), and their
corresponding values as values.
We will also add system checks that will make sure CORS settings are configured correctly
(if at all) when Django starts. The requirements for a valid configuration are -
We will also add system checks to make sure that the values in the CORS settings are of
the correct format and data type.
CSP
Similar to the proposed CORS protection spec, the CSP protection spec will provide the
following features -
1. CspMiddleware for adding the CSP header to responses. This will do the heavy
lifting of building the CSP policy and adding the CSP header.
2. CSP decorators -
a. csp_protect - A decorator that takes in all supported CSP directives as
arguments and adds the correct CSP header to the response. Adding this
decorator to a view will only add CSP directives for the arguments passed
to the decorator, and any additional global directives configured in the
settings will be ignored.
b. csp_update - A decorator that takes in all supported CSP directives as
arguments and adds the correct CSP header to the response. Adding this
header to a view will add directives for the passed arguments, as well as
any other global directives configured in the settings.
c. csp_exempt - A decorator that will exempt a view from CSP protection.
d. csp_use_policy - A decorator that will define which CSP policy to use for
a particular view.
3. CSP settings - We will provide a single setting for configuring the CSP policies -
CSP_POLICIES, which will map policy names to CSP directives. Moreover, we will
provide a CSP_DEFAULT_POLICY setting, which will define the CSP policy that will
be sent by default. The CSP_POLICIES setting will be a dictionary where the keys
will be the policy name and the values will be dictionaries specifying the policy. A
policy will be specified with directives as keys and strings as their values. There are
many such directives, the full list is given below.
a. DEFAULT_SRC
b. SCRIPT_SRC
c. SCRIPT_SRC_ATTR
d. SCRIPT_SRC_ELEM
e. IMG_SRC
f. OBJECT_SRC
g. PREFETCH_SRC
h. MEDIA_SRC
i. FRAME_SRC
j. FONT_SRC
k. CONNECT_SRC
l. STYLE_SRC
m. STYLE_SRC_ATTR
n. STYLE_SRC_ELEM
o. BASE_URI
p. CHILD_SRC
q. FRAME_ANSCESTORS
r. NAVIGATE_TO
s. FORM_ACTION
t. SANDBOX
u. REPORT_URI
v. REPORT_TO
w. MANIFEST_SRC
x. WORKER_SRC
y. PLUGIN_TYPES
z. REQUIRE_SRI_FOR
aa. UPGRADE_INSECURE_REQUESTS
bb. REQUIRE_TRUSTED_TYPES_FOR
cc. TRUSTED_TYPES
dd. BLOCK_ALL_MIXED_CONTENT
ee. REPORT_ONLY
ff. INCLUDE_NONCE_IN
gg. REPORT_PERCENTAGE
Most sites which need CSP will need just one policy, which will be named “default” at
project start. The default policy will be empty when a new project is created. An example
policy configuration for a project may look like the following -
CSP_DEFAULT_POLICY = “default”
CSP_POLICIES = {
“default”: {
DEFAULT_SRC: [“‘self’”],
SCRIPT_SRC: [“‘self’”, “cdn.example.com”]
},
“report-only”: {
REPORT_ONLY: True,
REPORT_URI: “/report-uri”,
OBJECT_SRC: None,
DEFAULT_SRC: [“https:”],
}
}
The implementation details for the middleware, settings, and decorators are provided
under the Implementation Details heading.
CSP protection will be disabled by default. Configuring CSP protection will involve adding
CspMiddleware to the list of middlewares, and configuring the CSP_POLICY setting.
Once configured, CspMiddleware will construct and send the CSP header as defined by
the CSP_DEFAULT_POLICY setting. Specific views will be able to send a different policy
by using the csp_use_policy decorator, and specific views (if any, although not
recommended) will be exempted by use of the csp_exempt decorator. We will not
provide a setting to only send CSP headers to specific paths, as in case of CORS, and CSP
headers will be sent to all paths. This behavior is to ensure that CSP protection is paranoid
and restrictive by default, since excluding any path in a project will eliminate the benefits
of CSP everywhere in the project [see Reference].
Since inline scripts will not execute once CSP is configured, we will provide nonce support.
CspMiddleware will generate a nonce value and add it as an attribute to the request
object as request.csp_nonce. Users will then need to add the nonce attribute to each
inline script tag in their templates. The INCLUDE_NONCE_IN setting will accept directives
as values, and add the CSP nonce to all the directives passed to it.
To make it more convenient to work with the CSP nonce, we will include a context
processor that will add a variable CSP_NONCE to the global template context, as a
shorthand for request.csp_nonce.
The REPORT_PERCENTAGE setting will allow users to limit the proportion of CSP
violations reported to the REPORT_URI. This may be useful in case users expect a lot of
violations to be reported. The default value of REPORT_PERCENTAGE will be 1 (100%).
We will also add system checks to make sure CSP settings are configured correctly, and
also to warn users if their policies contain any obvious weaknesses (such as the use of
‘unsafe-inline’, etc.)
Implementation Details
CORS
CORS will be implemented in the following files -
1. CorsMiddleware will be implemented in django.middleware.cors, following
the design of CsrfMiddleware
2. CORS decorators will be implemented in django.views.decorators.cors,
following the design of CSRF decorators
3. CORS settings will be added to django.conf.global_settings with their
defaults.
4. CORS system checks will be implemented in
django.core.checks.security.cors
CORS Settings
The default values for CORS settings will be
CORS_ALLOWED_ORIGINS = []
CORS_ALLOW_ALL_ORIGINS will not be defined
CORS_PATHS will not be defined
CORS_ALLOWED_METHODS = [
“DELETE”, “GET”, “OPTIONS”, “PATCH”, “POST”, “PUT”
]
CORS_ALLOWED_HEADERS = [
"accept", "accept-encoding", "authorization",
"content-type", "dnt", "origin", "user-agent",
"x-csrftoken", "x-requested-with",
]
CORS_EXPOSED_HEADERS = []
CORS_PREFLIGHT_MAX_AGE = 86400
CORS_ALLOW_CREDENTIALS = False
CORS Middleware
CorsMiddleware will implement process_request and process_response. During
process_request, CorsMiddleware will check if CORS headers are to be sent for this
request, and add a _cors_enabled attribute to the request. process_request will also
return responses for CORS preflight requests.
During process_response, we will add the actual headers to the response, and return
the response.
CORS Decorators
This section details the signatures of all CORS decorators and gives some usage examples
for decorators for which the usage may not be completely unambiguous from their
description.
1. @cors_protect(
allowed_origins: Sequence[str | Pattern[str]],
allow_all_origins: bool,
allowed_methods: Sequence[str],
allowed_headers: Sequence[str],
exposed_headers: Sequence[str],
preflight_max_age: int,
allow_credentials: bool,
policy_func: Callable[[HttpRequest], dict]
)
@cors_protect(
allow_all_origins=True,
allow_credentials=False
)
def my_view(request):
return render(request, ‘template.html’)
2. @cors_update(
allowed_origins: Sequence[str | Pattern[str]],
allow_all_origins: bool,
allowed_methods: Sequence[str],
allowed_headers: Sequence[str],
exposed_headers: Sequence[str],
preflight_max_age: int,
allow_credentials: bool,
policy_func: Callable[[HttpRequest], dict]
)
@cors_update(preflight_max_age=3600)
def my_view(request):
return render(request, ‘template.html’)
3. @cors_exempt()
def policy_func(request):
if request.headers[‘Origin’] == ‘api.example.com’:
preflight_max_age = 86400
else:
preflight_max_age = 3600
return {preflight_max_age: preflight_max_age}
For convenience and consistency, we will provide constants/default values for CORS
headers and methods, which will be the same as the ones that django-cors-headers
provides.
CSP
CSP will be implemented in the following files -
1. CspMiddleware will be implemented in django.middleware.csp
2. CSP decorators will be implemented in django.views.decorators.csp
3. CSP settings will be added to django.conf.global_settings with their
default values.
4. The CSP context processor will be implemented in
django.template.context_processors
5. CSP system checks will be implemented in
django.core.checks.security.csp
CSP Middleware
CspMiddleware will implement process_request, where it will generate the CSP nonce
and attach it to the request object. It will also implement process_response, where it
will build the actual policy from configured settings and add the Content-Security-Policy
header to the response.
CspMiddleware will have utility methods to build the CSP policy as well as generate the
CSP nonce. The nonce will be generated similarly to how django-csp does it, by using
os.urandom(16).
CSP Settings
The default values for CSP settings will be
CSP_DEFAULT_POLICY = “default”
CSP_POLICIES = {
“default”: {}
}
The default CSP policy will be empty, since it is not feasible for Django to determine what
kind of default policy a project will need when a new project is created.
CSP Decorators
This section details the signatures of all CSP decorators and gives some usage examples
for decorators for which the usage may not be completely unambiguous from their
description. The csp_protect and csp_update decorator will take in all CSP settings as
arguments (in lowercase). There are 32 such settings, so I won’t list them all again here.
1. @csp_protect(**kwargs)
@csp_protect(script_src=[“‘self’”])
def my_view(request):
return render(request, ‘template.html’)
2. @csp_update(**kwargs)
@csp_update(include_nonce_in=[“script-src”])
def my_view(request):
return render(request, ‘template.html’)
3. @csp_exempt()
4. @csp_use_policy(policy: str)
@csp_use_policy(“report-only”)
def my_view(request):
return render(request, ‘template.html’)
Timeline
4th May 2023 to 28th May 2023 (Community Bonding Period)
● Set up test projects
● Set up testing environments for testing CORS and CSP
● Finalize any pending points of the proposal
● Discuss proposal specifics with community
● Create ticket(s) for new features - CORS and CSP
Stretch Goals
1. Multiple CSP policies - It would be good to support sending multiple CSP policies,
either via the decorator, or via the CSP_DEFAULT_POLICY setting, or both. This
behavior is supported by browsers by simply sending multiple CSP headers for a
response, but it is harder to implement into Django, since response headers are
stored in a dictionary, which means we can’t specify the
Content-Security-Policy header more than once. To support multiple CSP
policies we will have to combine multiple policies into one, by considering the most
restrictive values for each directive, and then set the Content-Security-Policy
header once.
2. Hash based CSP - Support for hash based CSP would improve the security of all
Django projects. This could possibly be done by adding a setting called
SCRIPT_SRC_HASHES which would contain the hash values of the scripts the user
wants to include, but this idea doesn’t seem like the best approach and could be
refined.
3. Removing inline JavaScript from Admin GIS - Currently there is some inline
JavaScript in contrib/gis/templates/gis/openlayers.html and
contrib/gis/templates/gis/openlayers-osm.html, which will not work if
a content security policy is applied. Removing the inline JavaScript from the
template is non-trivial, but there is a closed PR by Claude P which shows roughly
how it can be done.
References
https://github.com/adamchainz/django-cors-headers/issues/578 - CORS headers not sent
for error responses
https://github.com/adamchainz/django-cors-headers/issues/422
About Me
Hi, my name is Hrushikesh Vaidya. I’m currently a second year engineering student at
the College of Engineering, Pune, India, majoring in electronics engineering with a
minor in computer science.
I’ve been programming in Python for 4 years, and working with Django for around 3
years. I’ve worked on a few Trac tickets over the last few months. I submitted a GSoC
proposal to add rate limiting to Django core in 2022. You can find that proposal and the
related discussion on the Django Forum.
My biggest & longest project so far has been a website offering an educational suite to
Indian teachers - including virtual classrooms, online student testing and analytics, and
much more. It is hosted at classberg.com. I sold a customized installation of the source
code for the same website to my old tuition class, and installed it on a subdomain on
their domain.