Code Execution in Jupyter Notebook Exports

After our research on Cursor, in the context of developer-ecosystem security, we turn our attention to the Jupyter ecosystem. We expose security risks we identified in the notebook’s export functionality, in the default Windows environment, to help organizations better protect their assets and networks.

Executive Summary

We identified a new way external Jupyter notebooks could be exploited by threat actors to lure unsuspecting users and compromise their workstation.

Companies are recommended to use a centralized Jupyter server, stay up to date and strictly restrict external files susceptible to processing with Jupyter software.

Introduction

Jupyter notebook is quite an institution in the development of AI projects. Back in 2015, around 200,000 notebooks were publicly available on GitHub—by early 2021 that number had surged to nearly 10 million. Used by more than 80 % of data scientists and AI engineers worldwide, Jupyter is deeply embedded in every stage of AI workflows, from exploratory analysis and visualization to model prototyping and collaboration.

When investigating this ecosystem, our approach was to try to imagine where a threat actor could find his way through, and leverage functionalities to exploit victims’ environments. The first direction came surprisingly easily: the configuration files.

Configuration files are often considered innocuous. However, they may include obscure parameters that most users aren’t aware of. Ignoring them would be a critical mistake.

Config files have led to vulnerabilities in many other instances. For example, in VSCode’s IDE, the .vscode/settings.json config file was also a key component in multiple high severity vulnerabilities discovered (CVE‑2021‑34529 , CVE‑2025‑53773 or CVE-2025-54130).

One specificity of the Jupyter ecosystem that makes this attack vector even more interesting is the fact that configuration files are also perfectly valid Python executables- making them easier to exploit.

Jupyter Configuration Files

The most common configuration file is jupyter_notebook_config.py, typically found in the user-specific configuration directory (~/.jupyter/). It’s responsible for defining core Notebook server settings such as network bindings, authentication options, file system paths, and various security-related parameters. However, other config files may also be used depending on the component, such as jupyter_nbconvert_config.py for export settings, or jupyter_server_config.py for Jupyter Server.

Configuration files can actually exist in any directory, allowing for layered overrides. Available options cover a wide range of functionality, from UI behavior and authentication to kernel management, export formats, logging, and more. This approach gives users fine-grained control over the entire Jupyter ecosystem.

For example:

c = get_config()
c.NotebookApp.port = 8888
c.FileContentsManager.save_script = True

However, acknowledging a high severity impact, Jupyter decided in October 2022 to remove CWD from the config paths, reducing the risk presented significantly.

This was the starting point of our research. We started searching for a similar or stronger way to exploit the same idea: having a file whose name is not constrained adjacent to a jupyter notebook, assuming an unsuspecting user would trigger an innocuous operation on a perfectly legit Jupyter notebook on the official Jupyter software and inadvertently allow full system compromise.

And this is exactly what we found by investigating the official export tool of Jupyter, nbconvert.

The Vulnerability

The vulnerability we discovered allows arbitrary code execution on Windows machines when exporting a notebook to PDF. By placing a properly named, malicious script in the notebook folder location, an attacker could hijack the conversion process and execute code with the privileges of the user.

When a Jupyter notebook containing SVG output is exported via nbconvert, the svg2pdf.py preprocessor is triggered to convert SVG images via the Inkscape tool. During this process, the path to Inkscape executable is resolved using Python’s shutil.which() via the following expression:

inkscape_path = which("inkscape")

without including inkscape anywhere as a mandatory nbconvert dependency. This opened the door to unintended code execution as the following figure shows:

Screenshot 2025 12 15 at 7.22.07 AM

Fig. 1: High level flow of exploitation of the security issue

shutil.which behavior is controlled internally by the Windows API function NeedCurrentDirectoryForExePathW, which returns TRUE (include CWD) when the NoDefaultCurrentDirectoryInExePath environment variable is not set, which is the default configuration on standard Windows installations.

In Python versions earlier than 3.12, `shutil.which()` ignores the `NoDefaultCurrentDirectoryInExePath` environment variable entirely, making it impossible to prevent this unsafe search behavior through configuration.

Python 3.12 and later versions properly respect this environment variable when set, but the variable remains unset by default on Windows systems, leaving many vulnerable.

Since nbconvert officially supports Python versions starting from 3.9, it includes versions that are affected by this issue both ways.

CVE-2025-53000

This unsafe lookup behavior aligns with CWE-427: Uncontrolled Search Path Element. Therefore, we recommended disabling the searching of inkscape software from CWD and relying on fixed safe search places.

Upon receiving our report, the Jupyter team reproduced the issue, acknowledged the associated risk, and requested a CVE (see below). A discussion was then initiated regarding how to fix the issue. However, the Jupyter team eventually stopped responding to our messages and has not addressed the issue to date.

CVE-2025-53000 has been assigned to this vulnerability. At the time of publication, the Github advisory has not yet been released by the maintainers.

Because export functionality is commonly used and generally trusted, it presents an attractive target for attackers, and especially in environments where notebooks are frequently shared—such as academic research groups, data science teams, or educational institutions—the potential for exploitation increases substantially.

Eventually, following our 90-day policy, we decided to publish this advisory to help protect the community.

Demonstration Video

The following demonstration video was recorded on a Windows 10 Enterprise x64 machine with default settings, using miniconda3 and Python 3.13.9, using the latest available Jupyter software versions, including:

Jupyter Core 5.9.1, nbconvert 7.16.6, and Notebook 7.5.0

Post Exploitation

Once successfully triggered, this vulnerability gives the attacker arbitrary code-execution in the context of the user. This immediately impacts confidentiality, integrity, and availability, as the attacker can access, modify, or disrupt the user’s data and workflows. On typical Windows data-science workstations, victim accounts almost always have:

  • Direct access to sensitive notebooks and datasets.
  • Cached cloud credentials (AWS CLI, Azure CLI, gcloud, Databricks etc.)
  • Locally installed package managers (conda, pip, winget) and DevOps pipelines that will happily run additional code.

This potentially amplifies the radius of compromise, allowing its effects to spread beyond the initial workstation.

Recommendations

Companies are recommended to rely on a centralized Jupyter server, ensure that all Jupyter-related software remains up to date, and enforce strict restrictions on external files that may be processed through Jupyter tools.

It is also recommended to enable the NoDefaultCurrentDirectoryInExePath environment variable to reduce the risk of unintentionally executing files from untrusted locations.

Conclusion

This vulnerability shows how the invisible glue of our workflows can become points of failure when not properly scrutinized.

We expect more vulnerabilities to surface in this fast-growing AI ecosystem as workflows become more automated, composable, and cloud-integrated, and we hope this report encourages teams to take a closer look at the quiet dependencies holding their environments together.

Timeline

  • June 8: Disclosure report submitted.
  • June 12: Issue reproduced.
  • June 25: CVE reservation by Jupyter team.

The post Code Execution in Jupyter Notebook Exports appeared first on Blog.