Skip to content

Commit

Permalink
Merge pull request #1367 from /issues/1329/1
Browse files Browse the repository at this point in the history
Fixes #1329, #1328. Add a CSP reporting endpoint and move security headers into app.
  • Loading branch information
karlcow authored Mar 2, 2017
2 parents 4f5a7bd + c8065f7 commit f5a9ec6
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 0 deletions.
4 changes: 4 additions & 0 deletions config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
if not LOCALHOST:
SESSION_COOKIE_SECURE = True

# By default, we want to log CSP violations. See /csp-report in views.py.
CSP_LOG = True

# Logging Capabilities
# To benefit from the logging, you may want to add:
# app.logger.info(Thing_To_Log)
Expand All @@ -38,6 +41,7 @@

LOG_FILE = '/tmp/webcompat.log'
LOG_FMT = '%(asctime)s tracking %(message)s'
CSP_REPORTS_LOG = '/tmp/webcompat-csp-reports.log'

# Status categories used in the project
# 'new', 'needsdiagnosis', 'needscontact', 'contactready' , 'sitewait', 'close'
Expand Down
13 changes: 13 additions & 0 deletions tests/test_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ def test_labeler_webhook(self):
# A random post should 401, only requests from GitHub will 200
self.assertEqual(rv.status_code, 401)

def test_csp_report_uri(self):
'''Test POST to /csp-report w/ correct content-type returns 204.'''
headers = {'Content-Type': 'application/csp-report'}
rv = self.app.post('/csp-report', headers=headers)
self.assertEqual(rv.status_code, 204)

def test_csp_report_uri_bad_content_type(self):
'''Test POST w/ wrong content-type to /csp-report returns 400.'''
headers = {'Content-Type': 'application/json'}
rv = self.app.post('/csp-report', headers=headers)
self.assertNotEqual(rv.status_code, 204)
self.assertEqual(rv.status_code, 400)

def test_tools_cssfixme(self):
'''Test that the /tools/cssfixme route gets 200.'''
rv = self.app.get('/tools/cssfixme')
Expand Down
31 changes: 31 additions & 0 deletions webcompat/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,3 +487,34 @@ def policy(*args, **kwargs):
return response
return update_wrapper(policy, view)
return set_policy


def add_sec_headers(response):
'''Add security-related headers to the response.
This should be used in @app.after_request to ensure the headers are
added to all responses.'''
if not app.config['LOCALHOST']:
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' # nopep8
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['X-Frame-Options'] = 'DENY'


def add_csp(response):
'''Add a Content-Security-Policy header to response.
This should be used in @app.after_request to ensure the header is
added to all responses.'''
# short term, we send Content-Security-Policy-Report-Only
# see https://github.com/webcompat/webcompat.com/issues/763 for
# sending Content-Security-Policy
response.headers['Content-Security-Policy-Report-Only'] = (
"default-src 'none'; " +
"connect-src 'self'; " +
"font-src 'self'; " +
"img-src 'self'; " +
"script-src 'self' https://www.google-analytics.com; " +
"style-src 'self'; " +
"report-uri /csp-report"
)
23 changes: 23 additions & 0 deletions webcompat/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

from form import AUTH_REPORT
from form import PROXY_REPORT
from helpers import add_csp
from helpers import add_sec_headers
from helpers import cache_policy
from helpers import get_browser_name
from helpers import get_form
Expand Down Expand Up @@ -52,6 +54,8 @@ def before_request():
@app.after_request
def after_request(response):
session_db.remove()
add_sec_headers(response)
add_csp(response)
return response


Expand Down Expand Up @@ -315,3 +319,22 @@ def contributors():
def cssfixme():
'''Route for CSS Fix me tool'''
return render_template('cssfixme.html')


@app.route('/csp-report', methods=['POST'])
def log_csp_report():
'''Route to record CSP header violations.
This route can be enabled/disabled by setting CSP_LOG to True/False
in config/__init__.py. It's enabled by default.
'''
expected_mime = 'application/csp-report'

if app.config['CSP_LOG']:
if expected_mime not in request.headers.get('content-type', ''):
return ('Wrong Content-Type.', 400)
with open(app.config['CSP_REPORTS_LOG'], 'a') as r:
r.write(request.data + '\n')
return ('', 204)
else:
return ('Forbidden.', 403)

0 comments on commit f5a9ec6

Please sign in to comment.