Repository: liberapay/liberapay.com

Issue:
Add X-XSS-Protection header
It would be a good idea to add the `X-XSS-Protection` header to Liberapay.

Gratipay's implementaion: https://github.com/gratipay/gratipay.com/blob/master/gratipay/security/__init__.py#L41-L42


Discussion:

Resolving patch:
diff --git a/liberapay/main.py b/liberapay/main.py
--- a/liberapay/main.py
+++ b/liberapay/main.py
@@ -15,7 +15,7 @@
 from liberapay.cron import Cron
 from liberapay.models.community import Community
 from liberapay.models.participant import Participant
-from liberapay.security import authentication, csrf, allow_cors_for_assets, x_frame_options
+from liberapay.security import authentication, csrf, set_default_security_headers
 from liberapay.utils import b64decode_s, b64encode_s, erase_cookie, http_caching, i18n, set_cookie
 from liberapay.utils.state_chain import (
     create_response_object, canonize, insert_constants,
@@ -117,8 +117,7 @@ def _assert(x):
     authentication.add_auth_to_response,
     csrf.add_token_to_response,
     http_caching.add_caching_to_response,
-    x_frame_options,
-    allow_cors_for_assets,
+    set_default_security_headers,
 
     algorithm['delegate_error_to_simplate'],
     tell_sentry,
diff --git a/liberapay/security/__init__.py b/liberapay/security/__init__.py
--- a/liberapay/security/__init__.py
+++ b/liberapay/security/__init__.py
@@ -1,21 +1,15 @@
 from __future__ import absolute_import, division, print_function, unicode_literals
 
 
-def allow_cors_for_assets(response, request=None):
-    """The subdomains need this to access the assets on the main domain.
-    """
+def set_default_security_headers(website, response, request=None):
+    # Allow CORS for assets
+    # The subdomains need this to access the assets on the main domain.
     if request is not None and request.path.raw.startswith('/assets/'):
         if b'Access-Control-Allow-Origin' not in response.headers:
             response.headers[b'Access-Control-Allow-Origin'] = b'*'
 
-
-def x_frame_options(response):
-    """X-Frame-Origin
-
-    This is a security measure to prevent clickjacking:
-    http://en.wikipedia.org/wiki/Clickjacking
-
-    """
+    # X-Frame-Options is a security measure to prevent clickjacking
+    # See http://en.wikipedia.org/wiki/Clickjacking
     if b'X-Frame-Options' not in response.headers:
         response.headers[b'X-Frame-Options'] = b'SAMEORIGIN'
     elif response.headers[b'X-Frame-Options'] == b'ALLOWALL':
@@ -31,3 +25,22 @@ def x_frame_options(response):
         #   http://ipsec.pl/node/1094
 
         del response.headers[b'X-Frame-Options']
+
+    # CSP is a client-side protection against code injection (XSS)
+    # https://scotthelme.co.uk/content-security-policy-an-introduction/
+    if b'content-security-policy' not in response.headers:
+        csp = (
+            b"default-src 'self';"
+            b"script-src 'self' 'unsafe-inline';"
+            b"style-src 'self' 'unsafe-inline';"
+            b"connect-src *;"  # for credit card data
+            b"img-src *;"
+            b"reflected-xss block;"
+        ) + website.app_conf.csp_extra.encode()
+        if website.canonical_scheme == 'https':
+            csp += b"upgrade-insecure-requests;block-all-mixed-content;"
+        response.headers[b'content-security-policy-report-only'] = csp
+
+    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
+    if b'X-XSS-Protection' not in response.headers:
+        response.headers[b'X-XSS-Protection'] = b'1; mode=block'
diff --git a/liberapay/wireup.py b/liberapay/wireup.py
--- a/liberapay/wireup.py
+++ b/liberapay/wireup.py
@@ -91,6 +91,7 @@ class AppConf(object):
         bountysource_secret=str,
         check_db_every=int,
         compress_assets=bool,
+        csp_extra=str,
         dequeue_emails_every=int,
         facebook_callback=str,
         facebook_id=str,


Test patch: