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.
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
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
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>
Header call sets a default policy which is later overriden based on the path to the Django view that's being used.
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.