diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py
index ae58e7a..fa58c2b 100644
--- a/IPython/html/base/handlers.py
+++ b/IPython/html/base/handlers.py
@@ -32,6 +32,8 @@ from IPython.utils.path import filefind
from IPython.utils.py3compat import string_types
from IPython.html.utils import is_hidden, url_path_join, url_escape
+from IPython.html.services.security import csp_report_uri
+
#-----------------------------------------------------------------------------
# Top-level handlers
#-----------------------------------------------------------------------------
@@ -45,17 +47,22 @@ class AuthenticatedHandler(web.RequestHandler):
def set_default_headers(self):
headers = self.settings.get('headers', {})
- if "X-Frame-Options" not in headers:
- headers["X-Frame-Options"] = "SAMEORIGIN"
+ if "Content-Security-Policy" not in headers:
+ headers["Content-Security-Policy"] = (
+ "frame-ancestors 'self'; "
+ # Make sure the report-uri is relative to the base_url
+ "report-uri " + url_path_join(self.base_url, csp_report_uri) + ";"
+ )
+ # Allow for overriding headers
for header_name,value in headers.items() :
try:
self.set_header(header_name, value)
- except Exception:
+ except Exception as e:
# tornado raise Exception (not a subclass)
# if method is unsupported (websocket and Access-Control-Allow-Origin
# for example, so just ignore)
- pass
+ self.log.debug(e)
def clear_login_cookie(self):
self.clear_cookie(self.cookie_name)
diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py
index e21db1a..24d18e8 100644
--- a/IPython/html/notebookapp.py
+++ b/IPython/html/notebookapp.py
@@ -225,7 +225,7 @@ class NotebookWebApplication(web.Application):
handlers.extend(load_handlers('services.sessions.handlers'))
handlers.extend(load_handlers('services.nbconvert.handlers'))
handlers.extend(load_handlers('services.kernelspecs.handlers'))
-
+ handlers.extend(load_handlers('services.security.handlers'))
handlers.append(
(r"/nbextensions/(.*)", FileFindHandler, {
'path': settings['nbextensions_path'],
diff --git a/IPython/html/services/kernels/tests/test_kernels_api.py b/IPython/html/services/kernels/tests/test_kernels_api.py
index 8f29a07..b33142c 100644
--- a/IPython/html/services/kernels/tests/test_kernels_api.py
+++ b/IPython/html/services/kernels/tests/test_kernels_api.py
@@ -65,7 +65,10 @@ class KernelAPITest(NotebookTestBase):
self.assertEqual(r.status_code, 201)
self.assertIsInstance(kern1, dict)
- self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN")
+ self.assertEqual(r.headers['Content-Security-Policy'], (
+ "frame-ancestors 'self'; "
+ "report-uri /api/security/csp-report;"
+ ))
def test_main_kernel_handler(self):
# POST request
@@ -75,7 +78,10 @@ class KernelAPITest(NotebookTestBase):
self.assertEqual(r.status_code, 201)
self.assertIsInstance(kern1, dict)
- self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN")
+ self.assertEqual(r.headers['Content-Security-Policy'], (
+ "frame-ancestors 'self'; "
+ "report-uri /api/security/csp-report;"
+ ))
# GET request
r = self.kern_api.list()
diff --git a/IPython/html/services/security/__init__.py b/IPython/html/services/security/__init__.py
new file mode 100644
index 0000000..9cf0d47
--- /dev/null
+++ b/IPython/html/services/security/__init__.py
@@ -0,0 +1,4 @@
+# URI for the CSP Report. Included here to prevent a cyclic dependency.
+# csp_report_uri is needed both by the BaseHandler (for setting the report-uri)
+# and by the CSPReportHandler (which depends on the BaseHandler).
+csp_report_uri = r"/api/security/csp-report"
diff --git a/IPython/html/services/security/handlers.py b/IPython/html/services/security/handlers.py
new file mode 100644
index 0000000..18f7874
--- /dev/null
+++ b/IPython/html/services/security/handlers.py
@@ -0,0 +1,23 @@
+"""Tornado handlers for security logging."""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from tornado import gen, web
+
+from ...base.handlers import IPythonHandler, json_errors
+from . import csp_report_uri
+
+class CSPReportHandler(IPythonHandler):
+ '''Accepts a content security policy violation report'''
+ @web.authenticated
+ @json_errors
+ def post(self):
+ '''Log a content security policy violation report'''
+ csp_report = self.get_json_body()
+ self.log.warn("Content security violation: %s",
+ self.request.body.decode('utf8', 'replace'))
+
+default_handlers = [
+ (csp_report_uri, CSPReportHandler)
+]
diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst
index b003dfa..87b41c5 100644
--- a/docs/source/whatsnew/development.rst
+++ b/docs/source/whatsnew/development.rst
@@ -180,16 +180,42 @@ Backwards incompatible changes
.. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT.
-IFrame embedding
-````````````````
+Content Security Policy
+```````````````````````
-The IPython Notebook and its APIs by default will only be allowed to be
-embedded in an iframe on the same origin.
+The Content Security Policy is a web standard for adding a layer of security to
+detect and mitigate certain classes of attacks, including Cross Site Scripting
+(XSS) and data injection attacks. This was introduced into the notebook to
+ensure that the IPython Notebook and its APIs (by default) can only be embedded
+in an iframe on the same origin.
-To override this, set ``headers[X-Frame-Options]`` to one of
+Override ``headers['Content-Security-Policy']`` within your notebook
+configuration to extend for alternate domains and security settings.::
-* DENY
-* SAMEORIGIN
-* ALLOW-FROM uri
+ c.NotebookApp.tornado_settings = {
+ 'headers': {
+ 'Content-Security-Policy': "frame-ancestors 'self'"
+ }
+ }
-See `Mozilla's guide to X-Frame-Options `_ for more examples.
+Example policies::
+
+ Content-Security-Policy: default-src 'self' https://*.jupyter.org
+
+Matches embeddings on any subdomain of jupyter.org, so long as they are served
+over SSL.
+
+There is a `report-uri `_ endpoint available for logging CSP violations, located at
+``/api/security/csp-report``. To use it, set ``report-uri`` as part of the CSP::
+
+ c.NotebookApp.tornado_settings = {
+ 'headers': {
+ 'Content-Security-Policy': "frame-ancestors 'self'; report-uri /api/security/csp-report"
+ }
+ }
+
+It simply provides the CSP report as a warning in IPython's logs. The default
+CSP sets this report-uri relative to the ``base_url`` (not shown above).
+
+For a more thorough and accurate guide on Content Security Policies, check out
+`MDN's Using Content Security Policy `_ for more examples.