CVE-2023-49080: Generation of Error Message Containing Sensitive Information (CWE-209)
value: The Jupyter Server provides the backend (i.e. the core services, APIs, and REST endpoints) for Jupyter web applications like Jupyter notebook, JupyterLab, and Voila. Unhandled errors in API requests coming from an authenticated user include traceback information, which can include path information. There is no known mechanism by which to trigger these errors without authentication, so the paths revealed are not considered particularly sensitive, given that the requesting user has arbitrary execution permissions already in the same environment. A fix has been introduced in commit `0056c3aa52` which no longer includes traceback information in JSON error responses. For compatibility, the traceback field is present, but always empty. This commit has been included in version 2.11.2. Users are advised to upgrade. There are no known workarounds for this vulnerability.
CWE description: The product generates an error message that includes sensitive information about its environment, users, or associated data.
CVSS v3 base score: 4.3
Published: 2023-12-04T21:15Z
Repository: https://github.com/jupyter-server/jupyter_server
Language: Python
Files changed: jupyter_server/base/handlers.py, jupyter_server/services/kernels/handlers.py
Commit date: 2023-12-04 10:36:43 -0600
Commit message: Merge pull request from GHSA-h56g-gq9v-vc8r

Co-authored-by: Steven Silvester <steven.silvester@ieee.org>
Diff: diff --git a/jupyter_server/base/handlers.py b/jupyter_server/base/handlers.py
index e6e6b3246e..b1b783cca9 100644
--- a/jupyter_server/base/handlers.py
+++ b/jupyter_server/base/handlers.py
@@ -8,11 +8,10 @@ import inspect
 import ipaddress
 import json
 import mimetypes
 import os
 import re
-import traceback
 import types
 import warnings
 from http.client import responses
 from logging import Logger
 from typing import TYPE_CHECKING, Any, Awaitable, Sequence, cast
@@ -746,11 +745,13 @@ class APIHandler(JupyterHandler):
                 reply["message"] = e.log_message or message
                 reply["reason"] = e.reason
             else:
                 reply["message"] = "Unhandled error"
                 reply["reason"] = None
-                reply["traceback"] = "".join(traceback.format_exception(*exc_info))
+                # backward-compatibility: traceback field is present,
+                # but always empty
+                reply["traceback"] = ""
         self.log.warning("wrote error: %r", reply["message"], exc_info=True)
         self.finish(json.dumps(reply))
 
     def get_login_url(self) -> str:
         """Get the login url."""
diff --git a/jupyter_server/services/kernels/handlers.py b/jupyter_server/services/kernels/handlers.py
index 2fb753e93c..217f0c9cc2 100644
--- a/jupyter_server/services/kernels/handlers.py
+++ b/jupyter_server/services/kernels/handlers.py
@@ -3,11 +3,10 @@
 Preliminary documentation at https://github.com/ipython/ipython/wiki/IPEP-16%3A-Notebook-multi-directory-dashboard-and-URL-mapping#kernels-api
 """
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
 import json
-from traceback import format_tb
 
 try:
     from jupyter_client.jsonutil import json_default
 except ImportError:
     from jupyter_client.jsonutil import date_default as json_default
@@ -100,12 +99,11 @@ class KernelActionHandler(KernelsAPIHandler):
             try:
                 await km.restart_kernel(kernel_id)
             except Exception as e:
                 message = "Exception restarting kernel"
                 self.log.error(message, exc_info=True)
-                traceback = format_tb(e.__traceback__)
-                self.write(json.dumps({"message": message, "traceback": traceback}))
+                self.write(json.dumps({"message": message, "traceback": ""}))
                 self.set_status(500)
             else:
                 model = await ensure_async(km.kernel_model(kernel_id))
                 self.write(json.dumps(model, default=json_default))
         self.finish()

Vulnerable code: import traceback
                reply["traceback"] = "".join(traceback.format_exception(*exc_info))
from traceback import format_tb
                traceback = format_tb(e.__traceback__)
                self.write(json.dumps({"message": message, "traceback": traceback}))
Fixed code:                 # backward-compatibility: traceback field is present,
                # but always empty
                reply["traceback"] = ""
                self.write(json.dumps({"message": message, "traceback": ""}))