Skip to content

fix (security): Sanitize output filenames in ExtractOutputPreprocessor to prevent path traversal - #2285

Open
LinZiyuu wants to merge 2 commits into
jupyter:mainfrom
LinZiyuu:fix/extractoutput-path-traversal
Open

fix (security): Sanitize output filenames in ExtractOutputPreprocessor to prevent path traversal#2285
LinZiyuu wants to merge 2 commits into
jupyter:mainfrom
LinZiyuu:fix/extractoutput-path-traversal

Conversation

@LinZiyuu

Copy link
Copy Markdown

Summary

ExtractOutputPreprocessor uses the notebook-provided output.metadata.filename as the on-disk filename for extracted outputs without sanitizing it. A crafted notebook can therefore cause extracted output files to be written outside the conversion output directory — both an absolute path and a ../ traversal escape — and for image/svg+xml outputs the written bytes are fully controlled by the notebook.

Details

# nbconvert/preprocessors/extractoutput.py (before)
if out.metadata.get("filename", ""):
    filename = out.metadata["filename"]                    # taken verbatim from the notebook
    ...
if output_files_dir is not None:
    filename = os.path.join(output_files_dir, filename)    # an absolute path discards the prefix
...
resources["outputs"][filename] = data

The default writer then writes each item without checking that the destination stays inside the build directory:

# nbconvert/writers/files.py
dest = os.path.join(build_dir, filename)   # an absolute path or a ../ sequence escapes build_dir
...
with open(dest, "wb") as f:
    f.write(data)

ExtractOutputPreprocessor is a default preprocessor for the rst, markdown, asciidoc, and latex/pdf exporters and FilesWriter is the default writer, so this code path is reached by an ordinary jupyter nbconvert invocation. The notebook does not need to be executed — the payload lives in static outputs.

Reproduction (before this change)

A notebook whose display output carries metadata.filename = "../../../../../../tmp/x/evil.svg" (or an absolute path) together with data["image/svg+xml"]:

$ jupyter nbconvert --to rst --output-dir build evil.ipynb
...
$ ls /tmp/x        # written OUTSIDE build/, with attacker-controlled contents
evil.svg

Fix

Reduce the output filename to its basename before use, and fall back to the generated output_filename_template when the basename is empty, so extracted outputs always stay inside the output directory. A warning is logged when a filename contained path components.

Tests

Adds regression tests covering ../ traversal, absolute paths, and empty-basename filenames in tests/preprocessors/test_extractoutput.py. The existing extractoutput tests continue to pass.

LinZiyuu and others added 2 commits May 24, 2026 22:57
…h traversal

ExtractOutputPreprocessor used the notebook-provided output.metadata.filename verbatim, so a crafted notebook could cause extracted output files to be written outside the output directory (both absolute paths and "../" sequences escape) with attacker-controlled contents when written by FilesWriter.

Reduce the filename to its basename before use, and fall back to the generated filename template when the basename is empty. Add regression tests covering traversal, absolute-path, and empty-basename cases.
@LinZiyuu LinZiyuu changed the title Sanitize output filenames in ExtractOutputPreprocessor to prevent path traversal fix (security): Sanitize output filenames in ExtractOutputPreprocessor to prevent path traversal May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant