Version in base suite: 7.16.6-1 Base version: nbconvert_7.16.6-1 Target version: nbconvert_7.16.6-1+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/n/nbconvert/nbconvert_7.16.6-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/n/nbconvert/nbconvert_7.16.6-1+deb13u1.dsc changelog | 10 + patches/0001-Merge-commit-from-fork.patch | 163 ++++++++++++++++++++++++++++++ patches/0002-Merge-commit-from-fork.patch | 83 +++++++++++++++ patches/series | 2 4 files changed, 258 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmph5u6ou01/nbconvert_7.16.6-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmph5u6ou01/nbconvert_7.16.6-1+deb13u1.dsc: no acceptable signature found diff -Nru nbconvert-7.16.6/debian/changelog nbconvert-7.16.6/debian/changelog --- nbconvert-7.16.6/debian/changelog 2025-02-09 15:45:36.000000000 +0000 +++ nbconvert-7.16.6/debian/changelog 2026-06-20 13:39:11.000000000 +0000 @@ -1,3 +1,13 @@ +nbconvert (7.16.6-1+deb13u1) trixie; urgency=medium + + * Non-maintainer upload. + * CVE-2026-39377: Arbitrary File Write via Path Traversal in + Cell Attachment Filenames (Closes: #1134889) + * CVE-2026-39378: Arbitrary File Read via Path Traversal in + HTMLExporter Image Embedding (Closes: #1134890) + + -- Adrian Bunk Sat, 20 Jun 2026 16:39:11 +0300 + nbconvert (7.16.6-1) unstable; urgency=medium * Team upload. diff -Nru nbconvert-7.16.6/debian/patches/0001-Merge-commit-from-fork.patch nbconvert-7.16.6/debian/patches/0001-Merge-commit-from-fork.patch --- nbconvert-7.16.6/debian/patches/0001-Merge-commit-from-fork.patch 1970-01-01 00:00:00.000000000 +0000 +++ nbconvert-7.16.6/debian/patches/0001-Merge-commit-from-fork.patch 2026-06-20 13:39:11.000000000 +0000 @@ -0,0 +1,163 @@ +From 573260af525a7b01a3c188159a15031ccd54e307 Mon Sep 17 00:00:00 2001 +From: James Hooker +Date: Wed, 8 Apr 2026 01:09:09 +0100 +Subject: Merge commit from fork + +Co-authored-by: g0blin +--- + nbconvert/preprocessors/extractattachments.py | 25 ++++- + .../preprocessors/test_extractattachments.py | 91 ++++++++++++++++++- + 2 files changed, 114 insertions(+), 2 deletions(-) + +diff --git a/nbconvert/preprocessors/extractattachments.py b/nbconvert/preprocessors/extractattachments.py +index 740e1960..096e7651 100644 +--- a/nbconvert/preprocessors/extractattachments.py ++++ b/nbconvert/preprocessors/extractattachments.py +@@ -82,6 +82,22 @@ class ExtractAttachmentsPreprocessor(Preprocessor): + for fname in cell.attachments: + self.log.debug("Encountered attachment %s", fname) + ++ # Sanitize: use only the basename to prevent path traversal ++ safe_fname = os.path.basename(fname) ++ if not safe_fname: ++ self.log.warning( ++ "Attachment filename '%s' is invalid (empty basename), skipping", ++ fname, ++ ) ++ continue ++ if safe_fname != fname: ++ self.log.warning( ++ "Attachment filename '%s' contained path components, " ++ "using basename '%s'", ++ fname, ++ safe_fname, ++ ) ++ + # Add file for writer + + # Right now I don't know of a situation where there would be multiple +@@ -94,7 +110,14 @@ class ExtractAttachmentsPreprocessor(Preprocessor): + break + + # FilesWriter wants path to be in attachment filename here +- new_filename = os.path.join(self.path_name, fname) ++ new_filename = os.path.join(self.path_name, safe_fname) ++ if new_filename in resources[self.resources_item_key]: ++ self.log.warning( ++ "Attachment filename '%s' (from '%s') overwrites a previous " ++ "attachment with the same name", ++ safe_fname, ++ fname, ++ ) + resources[self.resources_item_key][new_filename] = decoded + + # Edit the reference to the attachment +diff --git a/tests/preprocessors/test_extractattachments.py b/tests/preprocessors/test_extractattachments.py +index 739b6919..a2cbd1e2 100644 +--- a/tests/preprocessors/test_extractattachments.py ++++ b/tests/preprocessors/test_extractattachments.py +@@ -4,7 +4,9 @@ + # Distributed under the terms of the Modified BSD License. + + import os +-from base64 import b64decode ++from base64 import b64decode, b64encode ++ ++from nbformat import v4 as nbformat + + from nbconvert.preprocessors.extractattachments import ExtractAttachmentsPreprocessor + +@@ -86,3 +88,90 @@ class TestExtractAttachments(PreprocessorTestsBase): + src = nb.cells[-1].source + # This shouldn't change on Windows + self.assertEqual(src, "![image.png](notebook1_custom/image.png)") ++ ++ def test_attachment_path_traversal_sanitised(self): ++ """Test that path traversal in attachment filenames is sanitised. ++ ++ Crafted attachment filenames containing '../' sequences must not escape ++ the output directory. The preprocessor should strip path components, ++ store the file under its basename only, and update the cell source ++ reference accordingly. ++ """ ++ malicious_fname = "../../../../../../../tmp/nbconvert_traversal/evil.php" ++ malicious_content = b" +Date: Wed, 8 Apr 2026 01:09:43 +0100 +Subject: Merge commit from fork + +* Prevent path traversal when embedding images + +* Switch from realpath to abspath to avoid symlink abuse + +--------- + +Co-authored-by: g0blin +--- + nbconvert/filters/markdown_mistune.py | 5 +++++ + tests/exporters/test_html.py | 28 +++++++++++++++++++++++++++ + 2 files changed, 33 insertions(+) + +diff --git a/nbconvert/filters/markdown_mistune.py b/nbconvert/filters/markdown_mistune.py +index 67a6aa8f..80385a7f 100644 +--- a/nbconvert/filters/markdown_mistune.py ++++ b/nbconvert/filters/markdown_mistune.py +@@ -435,6 +435,11 @@ class IPythonRenderer(HTMLRenderer): + """ + src_path = os.path.join(self.path, src) + ++ resolved = os.path.abspath(src_path) ++ allowed_base = os.path.abspath(self.path) ++ if not resolved.startswith(allowed_base + os.sep) and resolved != allowed_base: ++ return None ++ + if not os.path.exists(src_path): + return None + +diff --git a/tests/exporters/test_html.py b/tests/exporters/test_html.py +index f1bfb69b..5d1577ed 100644 +--- a/tests/exporters/test_html.py ++++ b/tests/exporters/test_html.py +@@ -3,7 +3,10 @@ + # Copyright (c) IPython Development Team. + # Distributed under the terms of the Modified BSD License. + ++import base64 ++import os + import re ++from tempfile import TemporaryDirectory + + import pytest + from nbformat import v4 +@@ -264,6 +267,31 @@ class TestHTMLExporter(ExportersTestsBase): + + assert '' in output + ++ def test_embed_images_path_traversal_blocked(self): ++ """Path traversal in image src should be blocked when embed_images=True""" ++ with TemporaryDirectory() as parent_dir: ++ # Create a secret file that an attacker would try to exfiltrate ++ secret_content = b"SUPERSECRETCONTENT" ++ with open(os.path.join(parent_dir, "secret.txt"), "wb") as f: ++ f.write(secret_content) ++ ++ # Create a temp subdirectory to serve as the notebook's working path ++ with TemporaryDirectory(dir=parent_dir) as notebook_dir: ++ # Build a notebook with path traversal image references ++ nb = v4.new_notebook() ++ nb.cells.append(v4.new_markdown_cell("![exfil](../secret.txt)")) ++ nb.cells.append(v4.new_markdown_cell('exfil')) ++ ++ exporter = HTMLExporter() ++ exporter.embed_images = True ++ html, _ = exporter.from_notebook_node( ++ nb, resources={"metadata": {"path": notebook_dir}} ++ ) ++ ++ # The secret content must NOT appear as base64 in the output ++ target_b64 = base64.b64encode(secret_content).decode() ++ self.assertNotIn(target_b64, html) ++ + + @pytest.mark.parametrize( + ("lexer_options"), +-- +2.47.3 + diff -Nru nbconvert-7.16.6/debian/patches/series nbconvert-7.16.6/debian/patches/series --- nbconvert-7.16.6/debian/patches/series 2025-02-09 15:45:36.000000000 +0000 +++ nbconvert-7.16.6/debian/patches/series 2026-06-20 13:39:11.000000000 +0000 @@ -5,3 +5,5 @@ set-nbsphinx_allow_errors-in-sphinx-conf.patch dont-use-intersphinx-during-build.patch skip-test_default_config-due-to-jupyter-core-4.11.2.patch +0001-Merge-commit-from-fork.patch +0002-Merge-commit-from-fork.patch