Your message dated Sat, 11 Jul 2026 10:32:48 +0000
with message-id <[email protected]>
and subject line Released in 13.6
has caused the Debian Bug report #1140449,
regarding trixie-pu: package nbconvert/7.16.6-1+deb13u1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact [email protected]
immediately.)


-- 
1140449: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1140449
Debian Bug Tracking System
Contact [email protected] with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected], [email protected]
Control: affects -1 + src:nbconvert
User: [email protected]
Usertags: pu

  * 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)
diffstat for nbconvert-7.16.6 nbconvert-7.16.6

 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(+)

diff -Nru nbconvert-7.16.6/debian/changelog nbconvert-7.16.6/debian/changelog
--- nbconvert-7.16.6/debian/changelog   2025-02-09 17:45:36.000000000 +0200
+++ nbconvert-7.16.6/debian/changelog   2026-06-20 16:39:11.000000000 +0300
@@ -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 <[email protected]>  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 02:00:00.000000000 +0200
+++ nbconvert-7.16.6/debian/patches/0001-Merge-commit-from-fork.patch   
2026-06-20 16:39:11.000000000 +0300
@@ -0,0 +1,163 @@
+From 573260af525a7b01a3c188159a15031ccd54e307 Mon Sep 17 00:00:00 2001
+From: James Hooker <[email protected]>
+Date: Wed, 8 Apr 2026 01:09:09 +0100
+Subject: Merge commit from fork
+
+Co-authored-by: g0blin <[email protected]>
+---
+ 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"<?php\n// I should not be here"
++        b64_content = b64encode(malicious_content).decode("utf-8")
++        attachments = {malicious_fname: {"text/plain": b64_content}}
++        cell = nbformat.new_markdown_cell(
++            source=f"![exploit](attachment:{malicious_fname})",
++            attachments=attachments,
++        )
++        nb = nbformat.new_notebook(cells=[cell])
++        res = self.build_resources()
++        preprocessor = self.build_preprocessor()
++        nb, res = preprocessor(nb, res)
++
++        # The output key must be the basename only - no traversal components
++        self.assertIn("evil.php", res["outputs"])
++        self.assertEqual(res["outputs"]["evil.php"], malicious_content)
++        for key in res["outputs"]:
++            self.assertNotIn("..", key)
++            self.assertFalse(os.path.isabs(key))
++
++        # Cell source must reference the safe, flattened filename
++        self.assertEqual(nb.cells[0].source, "![exploit](evil.php)")
++
++    def test_attachment_absolute_path_sanitised(self):
++        """Test that absolute paths in attachment filenames are sanitised.
++
++        An absolute path like '/tmp/absolute/test1' would cause os.path.join
++        to discard the output directory prefix entirely, writing to the
++        absolute location. The preprocessor must strip the path and use
++        only the basename.
++        """
++        abs_fname = "/tmp/absolute/test1"
++        content = b"absolute path write test"
++        b64_content = b64encode(content).decode("utf-8")
++        attachments = {abs_fname: {"text/plain": b64_content}}
++        cell = nbformat.new_markdown_cell(
++            source=f"![file](attachment:{abs_fname})",
++            attachments=attachments,
++        )
++        nb = nbformat.new_notebook(cells=[cell])
++        res = self.build_resources()
++        preprocessor = self.build_preprocessor()
++        nb, res = preprocessor(nb, res)
++
++        # The output key must be the basename only - not the absolute path
++        self.assertIn("test1", res["outputs"])
++        self.assertNotIn("/tmp/absolute/test1", res["outputs"])
++        self.assertEqual(res["outputs"]["test1"], content)
++        for key in res["outputs"]:
++            self.assertFalse(os.path.isabs(key))
++
++        # Cell source must reference the safe, flattened filename
++        self.assertEqual(nb.cells[0].source, "![file](test1)")
++
++    def test_attachment_empty_basename_skipped(self):
++        """Test that filenames resolving to an empty basename are skipped.
++
++        A filename like '../../../tmp/' has an empty os.path.basename(),
++        which would cause downstream errors if not caught. The preprocessor
++        should skip these attachments entirely and log a warning.
++        """
++        bad_fname = "../../../tmp/"
++        b64_content = b64encode(b"should be skipped").decode("utf-8")
++        attachments = {bad_fname: {"text/plain": b64_content}}
++        cell = nbformat.new_markdown_cell(
++            source=f"![x](attachment:{bad_fname})",
++            attachments=attachments,
++        )
++        nb = nbformat.new_notebook(cells=[cell])
++        res = self.build_resources()
++        preprocessor = self.build_preprocessor()
++        nb, res = preprocessor(nb, res)
++
++        # No outputs should be created for empty basename
++        self.assertEqual(len(res["outputs"]), 0)
++
++        # Cell source should remain unchanged (attachment ref not rewritten)
++        self.assertEqual(nb.cells[0].source, f"![x](attachment:{bad_fname})")
+-- 
+2.47.3
+
diff -Nru nbconvert-7.16.6/debian/patches/0002-Merge-commit-from-fork.patch 
nbconvert-7.16.6/debian/patches/0002-Merge-commit-from-fork.patch
--- nbconvert-7.16.6/debian/patches/0002-Merge-commit-from-fork.patch   
1970-01-01 02:00:00.000000000 +0200
+++ nbconvert-7.16.6/debian/patches/0002-Merge-commit-from-fork.patch   
2026-06-20 16:39:11.000000000 +0300
@@ -0,0 +1,83 @@
+From de92f54b0575ca9489661591481afa8a6a4f7ee5 Mon Sep 17 00:00:00 2001
+From: James Hooker <[email protected]>
+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 <[email protected]>
+---
+ 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 '<html lang="en">' 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('<img 
src="../secret.txt" alt="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 17:45:36.000000000 
+0200
+++ nbconvert-7.16.6/debian/patches/series      2026-06-20 16:39:11.000000000 
+0300
@@ -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

--- End Message ---
--- Begin Message ---
Version: 13.6

This update was released as part of 13.6.

--- End Message ---

Reply via email to