VYPR
Medium severity6.1NVD Advisory· Published Sep 20, 2017· Updated May 13, 2026

CVE-2015-4707

CVE-2015-4707

Description

Cross-site scripting (XSS) vulnerability in IPython before 3.2 allows remote attackers to inject arbitrary web script or HTML via vectors involving JSON error messages and the /api/notebooks path.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
ipythonPyPI
< 3.2.03.2.0

Affected products

1

Patches

2
c2078a53543e

Set content type in json_error to application/json

https://github.com/ipython/ipythonKyle KelleyJun 22, 2015via ghsa
1 file changed · +2 0
  • IPython/html/base/handlers.py+2 0 modified
    @@ -339,6 +339,7 @@ def wrapper(self, *args, **kwargs):
                 message = e.log_message
                 self.log.warn(message)
                 self.set_status(e.status_code)
    +            self.set_header('Content-Type', 'application/json')
                 self.finish(json.dumps(dict(message=message)))
             except Exception:
                 self.log.error("Unhandled error in API request", exc_info=True)
    @@ -348,6 +349,7 @@ def wrapper(self, *args, **kwargs):
                 self.set_status(status)
                 tb_text = ''.join(traceback.format_exception(t, value, tb))
                 reply = dict(message=message, traceback=tb_text)
    +            self.set_header('Content-Type', 'application/json')
                 self.finish(json.dumps(reply))
             else:
                 return result
    
7222bd53ad08

Merge branch csp into 3.x

https://github.com/ipython/ipythonMin RKJun 21, 2015via ghsa
11 files changed · +65 38
  • IPython/html/base/handlers.py+34 9 modified
    @@ -42,16 +42,24 @@
     
     class AuthenticatedHandler(web.RequestHandler):
         """A RequestHandler with an authenticated user."""
    +    
    +    @property
    +    def content_security_policy(self):
    +        """The default Content-Security-Policy header
    +        
    +        Can be overridden by defining Content-Security-Policy in settings['headers']
    +        """
    +        return '; '.join([
    +            "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),
    +        ])
     
         def set_default_headers(self):
             headers = self.settings.get('headers', {})
     
             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) + ";"
    -            )
    +            headers["Content-Security-Policy"] = self.content_security_policy
     
             # Allow for overriding headers
             for header_name,value in headers.items() :
    @@ -307,7 +315,22 @@ def write_error(self, status_code, **kwargs):
                 html = self.render_template('error.html', **ns)
             
             self.write(html)
    -        
    +
    +
    +class APIHandler(IPythonHandler):
    +    """Base class for API handlers"""
    +    
    +    @property
    +    def content_security_policy(self):
    +        csp = '; '.join([
    +                super(APIHandler, self).content_security_policy,
    +                "default-src 'none'",
    +            ])
    +        return csp
    +    
    +    def finish(self, *args, **kwargs):
    +        self.set_header('Content-Type', 'application/json')
    +        return super(APIHandler, self).finish(*args, **kwargs)
     
     
     class Template404(IPythonHandler):
    @@ -370,13 +393,15 @@ def wrapper(self, *args, **kwargs):
             try:
                 result = yield gen.maybe_future(method(self, *args, **kwargs))
             except web.HTTPError as e:
    +            self.set_header('Content-Type', 'application/json')
                 status = e.status_code
                 message = e.log_message
                 self.log.warn(message)
                 self.set_status(e.status_code)
                 reply = dict(message=message, reason=e.reason)
                 self.finish(json.dumps(reply))
             except Exception:
    +            self.set_header('Content-Type', 'application/json')
                 self.log.error("Unhandled error in API request", exc_info=True)
                 status = 500
                 message = "Unknown server error"
    @@ -399,7 +424,7 @@ def wrapper(self, *args, **kwargs):
     # to minimize subclass changes:
     HTTPError = web.HTTPError
     
    -class FileFindHandler(web.StaticFileHandler):
    +class FileFindHandler(IPythonHandler, web.StaticFileHandler):
         """subclass of StaticFileHandler for serving files from a search path"""
         
         # cache search results, don't search for files more than once
    @@ -453,7 +478,7 @@ def validate_absolute_path(self, root, absolute_path):
             return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
     
     
    -class ApiVersionHandler(IPythonHandler):
    +class APIVersionHandler(APIHandler):
     
         @json_errors
         def get(self):
    @@ -524,5 +549,5 @@ def get(self, path=''):
     
     default_handlers = [
         (r".*/", TrailingSlashHandler),
    -    (r"api", ApiVersionHandler)
    +    (r"api", APIVersionHandler)
     ]
    
  • IPython/html/services/clusters/handlers.py+4 4 modified
    @@ -7,28 +7,28 @@
     
     from tornado import web
     
    -from ...base.handlers import IPythonHandler
    +from ...base.handlers import APIHandler
     
     #-----------------------------------------------------------------------------
     # Cluster handlers
     #-----------------------------------------------------------------------------
     
     
    -class MainClusterHandler(IPythonHandler):
    +class MainClusterHandler(APIHandler):
     
         @web.authenticated
         def get(self):
             self.finish(json.dumps(self.cluster_manager.list_profiles()))
     
     
    -class ClusterProfileHandler(IPythonHandler):
    +class ClusterProfileHandler(APIHandler):
     
         @web.authenticated
         def get(self, profile):
             self.finish(json.dumps(self.cluster_manager.profile_info(profile)))
     
     
    -class ClusterActionHandler(IPythonHandler):
    +class ClusterActionHandler(APIHandler):
     
         @web.authenticated
         def post(self, profile, action):
    
  • IPython/html/services/config/handlers.py+2 2 modified
    @@ -9,9 +9,9 @@
     from tornado import web
     
     from IPython.utils.py3compat import PY3
    -from ...base.handlers import IPythonHandler, json_errors
    +from ...base.handlers import APIHandler, json_errors
     
    -class ConfigHandler(IPythonHandler):
    +class ConfigHandler(APIHandler):
         SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH')
     
         @web.authenticated
    
  • IPython/html/services/contents/handlers.py+4 4 modified
    @@ -11,7 +11,7 @@
     from IPython.utils.jsonutil import date_default
     
     from IPython.html.base.handlers import (
    -    IPythonHandler, json_errors, path_regex,
    +    IPythonHandler, APIHandler, json_errors, path_regex,
     )
     
     
    @@ -75,7 +75,7 @@ def validate_model(model, expect_content):
                 )
     
     
    -class ContentsHandler(IPythonHandler):
    +class ContentsHandler(APIHandler):
     
         SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
     
    @@ -257,7 +257,7 @@ def delete(self, path=''):
             self.finish()
     
     
    -class CheckpointsHandler(IPythonHandler):
    +class CheckpointsHandler(APIHandler):
     
         SUPPORTED_METHODS = ('GET', 'POST')
     
    @@ -286,7 +286,7 @@ def post(self, path=''):
             self.finish(data)
     
     
    -class ModifyCheckpointsHandler(IPythonHandler):
    +class ModifyCheckpointsHandler(APIHandler):
     
         SUPPORTED_METHODS = ('POST', 'DELETE')
     
    
  • IPython/html/services/kernels/handlers.py+4 4 modified
    @@ -13,12 +13,12 @@
     from IPython.utils.py3compat import cast_unicode
     from IPython.html.utils import url_path_join, url_escape
     
    -from ...base.handlers import IPythonHandler, json_errors
    +from ...base.handlers import IPythonHandler, APIHandler, json_errors
     from ...base.zmqhandlers import AuthenticatedZMQStreamHandler, deserialize_binary_message
     
     from IPython.core.release import kernel_protocol_version
     
    -class MainKernelHandler(IPythonHandler):
    +class MainKernelHandler(APIHandler):
     
         @web.authenticated
         @json_errors
    @@ -46,7 +46,7 @@ def post(self):
             self.finish(json.dumps(model))
     
     
    -class KernelHandler(IPythonHandler):
    +class KernelHandler(APIHandler):
     
         SUPPORTED_METHODS = ('DELETE', 'GET')
     
    @@ -67,7 +67,7 @@ def delete(self, kernel_id):
             self.finish()
     
     
    -class KernelActionHandler(IPythonHandler):
    +class KernelActionHandler(APIHandler):
     
         @web.authenticated
         @json_errors
    
  • IPython/html/services/kernelspecs/handlers.py+3 3 modified
    @@ -10,7 +10,7 @@
     
     from tornado import web
     
    -from ...base.handlers import IPythonHandler, json_errors
    +from ...base.handlers import APIHandler, json_errors
     from ...utils import url_path_join
     
     def kernelspec_model(handler, name):
    @@ -40,7 +40,7 @@ def kernelspec_model(handler, name):
             )
         return d
     
    -class MainKernelSpecHandler(IPythonHandler):
    +class MainKernelSpecHandler(APIHandler):
         SUPPORTED_METHODS = ('GET',)
     
         @web.authenticated
    @@ -62,7 +62,7 @@ def get(self):
             self.finish(json.dumps(model))
     
     
    -class KernelSpecHandler(IPythonHandler):
    +class KernelSpecHandler(APIHandler):
         SUPPORTED_METHODS = ('GET',)
     
         @web.authenticated
    
  • IPython/html/services/kernels/tests/test_kernels_api.py+4 2 modified
    @@ -67,7 +67,8 @@ def test_default_kernel(self):
     
             self.assertEqual(r.headers['Content-Security-Policy'], (
                                 "frame-ancestors 'self'; "
    -                            "report-uri /api/security/csp-report;"
    +                            "report-uri /api/security/csp-report; "
    +                            "default-src 'none'"
             ))
     
         def test_main_kernel_handler(self):
    @@ -80,7 +81,8 @@ def test_main_kernel_handler(self):
     
             self.assertEqual(r.headers['Content-Security-Policy'], (
                                 "frame-ancestors 'self'; "
    -                            "report-uri /api/security/csp-report;"
    +                            "report-uri /api/security/csp-report; "
    +                            "default-src 'none'"
             ))
     
             # GET request
    
  • IPython/html/services/nbconvert/handlers.py+2 2 modified
    @@ -2,9 +2,9 @@
     
     from tornado import web
     
    -from ...base.handlers import IPythonHandler, json_errors
    +from ...base.handlers import APIHandler, json_errors
     
    -class NbconvertRootHandler(IPythonHandler):
    +class NbconvertRootHandler(APIHandler):
         SUPPORTED_METHODS = ('GET',)
     
         @web.authenticated
    
  • IPython/html/services/security/handlers.py+2 2 modified
    @@ -5,10 +5,10 @@
     
     from tornado import gen, web
     
    -from ...base.handlers import IPythonHandler, json_errors
    +from ...base.handlers import APIHandler, json_errors
     from . import csp_report_uri
     
    -class CSPReportHandler(IPythonHandler):
    +class CSPReportHandler(APIHandler):
         '''Accepts a content security policy violation report'''
         @web.authenticated
         @json_errors
    
  • IPython/html/services/sessions/handlers.py+3 3 modified
    @@ -7,13 +7,13 @@
     
     from tornado import web
     
    -from ...base.handlers import IPythonHandler, json_errors
    +from ...base.handlers import APIHandler, json_errors
     from IPython.utils.jsonutil import date_default
     from IPython.html.utils import url_path_join, url_escape
     from IPython.kernel.kernelspec import NoSuchKernel
     
     
    -class SessionRootHandler(IPythonHandler):
    +class SessionRootHandler(APIHandler):
     
         @web.authenticated
         @json_errors
    @@ -65,7 +65,7 @@ def post(self):
             self.set_status(201)
             self.finish(json.dumps(model, default=date_default))
     
    -class SessionHandler(IPythonHandler):
    +class SessionHandler(APIHandler):
     
         SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
     
    
  • IPython/html/terminal/api_handlers.py+3 3 modified
    @@ -1,9 +1,9 @@
     import json
     from tornado import web, gen
    -from ..base.handlers import IPythonHandler, json_errors
    +from ..base.handlers import APIHandler, json_errors
     from ..utils import url_path_join
     
    -class TerminalRootHandler(IPythonHandler):
    +class TerminalRootHandler(APIHandler):
         @web.authenticated
         @json_errors
         def get(self):
    @@ -19,7 +19,7 @@ def post(self):
             self.finish(json.dumps({'name': name}))
     
     
    -class TerminalHandler(IPythonHandler):
    +class TerminalHandler(APIHandler):
         SUPPORTED_METHODS = ('GET', 'DELETE')
     
         @web.authenticated
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

10

News mentions

0

No linked articles in our index yet.