Content Security Policy is a proposed HTTP extension which allows websites to restrict the external content that can be displayed by visiting web browsers. By expressing a set of rules to be enforced by the browser, a website is able to prevent the injection of outside resources by malicious users.
While adding support for the March 2011 draft in Libravatar, I looked at three different approaches.
Controlling the headers in the application
The first approach I considered was to have the Django application output all of the headers, which is what the django-csp module does. Unfortunately, I need to be able to vary the policy between pages (the views in Libravatar have different requirements) and that's one of the things that hasn't been implemented yet in that module.
Producing the same headers by hand is fairly simple:
response = render_to_response('app/view.html')
response['X-Content-Security-Policy'] = "allow 'self'"
return response
but it would mean adding a bit of code to every view and/or writing a custom wrapper for render_to_response()
.
Setting a default header in Apache
Ideally, I'd like to be able to set a default header in Apache using mod_headers and then override it as needed inside the application.
The first problem with this solution is that it's not possible (as far I can tell) for a Django application to override a header set by Apache:
- mod_headers adds its response header after mod_wsgi has returned (unless early processing is used).
- Django's response objects cannot see the early headers set by Apache.
The second problem is that mod_headers doesn't have an action that adds/sets a header only if it didn't already exist. It does have append
and merge
actions which could in theory be used to add extra terms to the policy but it unfortunately uses a different separator (the comma) from the CSP spec (which uses semi-colons).
Always set headers in Apache
While I would have liked to get the second approach working, in the end, I included all of the CSP directives within the main Apache config file:
Header set X-Content-Security-Policy: "allow 'self'; options inline-script; img-src 'self' data:"
<Location /account/confirm_email>
Header set X-Content-Security-Policy: "allow 'self'; options inline-script; img-src *"
</Location>
<Location /tools/check>
Header set X-Content-Security-Policy: "allow 'self'; options inline-script; img-src *"
</Location>
The first Header
call sets a default policy which is later overriden based on the path to the Django view that's being used.
Related technologies
If you are interested in Content Security Policy, you may also want to look into Application Boundaries Enforcer (part of the NoScript Firefox extension) for more security rules that can be supplied by the server and enforced client-side.
It's also worth mentioning the excellent Request Policy extension which solves the same problem by letting users whitelist the cross-site requests they want to allow.
RequestPolicy is now in Debian:
http://packages.debian.org/sid/xul-ext-requestpolicy
Django's middleware would be the best way to handle this, I think. See the X-Frame-Options middleware which sets headers to prevent clickjacking, for instance:
https://docs.djangoproject.com/en/dev/ref/middleware/#module-django.middleware.clickjacking
Or if you didn't want to use the middleware, you could just stick your bit of processing code in a decorator.
There are plenty of ways to do it. None of them particularly difficult. Doing it from django is probably the best way.