Skip to content

Commit c9ac1d1

Browse files
h3pdesignminrk
andauthored
Fix CVE-2025-53000: Secure Inkscape Windows path (registry first + block CWD) (#2261)
Co-authored-by: Min RK <benjaminrk@gmail.com>
1 parent b13276d commit c9ac1d1

File tree

1 file changed

+47
-11
lines changed

1 file changed

+47
-11
lines changed

nbconvert/preprocessors/svg2pdf.py

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import os
1010
import subprocess
1111
import sys
12+
import warnings
13+
from pathlib import Path
1214
from shutil import which
1315
from tempfile import TemporaryDirectory
1416

@@ -101,26 +103,60 @@ def _command_default(self):
101103

102104
@default("inkscape")
103105
def _inkscape_default(self):
106+
# Windows: Secure registry lookup FIRST (CVE-2025-53000 fix)
107+
if sys.platform == "win32":
108+
wr_handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
109+
try:
110+
rkey = winreg.OpenKey(wr_handle, r"SOFTWARE\Classes\inkscape.svg\DefaultIcon")
111+
inkscape_full = winreg.QueryValueEx(rkey, "")[0].split(",")[0] # Fix: remove ",0"
112+
if os.path.isfile(inkscape_full):
113+
return inkscape_full
114+
except (FileNotFoundError, OSError, IndexError):
115+
pass # Safe fallback
116+
117+
# Block CWD in PATH search (CVE-2025-53000)
118+
os.environ["NODEFAULTCURRENTDIRECTORYINEXEPATH"] = "1"
119+
104120
inkscape_path = which("inkscape")
121+
122+
# Extra safety for Python < 3.12 on Windows:
123+
# If which() resolved to a path in CWD even though CWD is not on PATH,
124+
# warn and treat as "not found".
125+
if sys.platform == "win32" and inkscape_path and sys.version_info < (3, 12):
126+
try:
127+
cwd = Path.cwd().resolve()
128+
in_cwd = Path(inkscape_path).resolve().parent == cwd
129+
cwd_on_path = cwd in {
130+
Path(p).resolve() for p in os.environ.get("PATH", os.defpath).split(os.pathsep)
131+
}
132+
133+
if in_cwd and not cwd_on_path:
134+
warnings.warn(
135+
"shutil.which('inkscape') resolved to an executable in the current "
136+
"working directory even though CWD is not on PATH. Ignoring this "
137+
"result for security reasons (CVE-2025-53000).",
138+
RuntimeWarning,
139+
stacklevel=2,
140+
)
141+
inkscape_path = None
142+
except Exception:
143+
# If detection fails for any reason, prefer safety: ignore CWD result
144+
inkscape_path = None
145+
105146
if inkscape_path is not None:
106147
return inkscape_path
148+
149+
# macOS: EXACT original order preserved
107150
if sys.platform == "darwin":
108151
if os.path.isfile(INKSCAPE_APP_v1):
109152
return INKSCAPE_APP_v1
110153
# Order is important. If INKSCAPE_APP exists, prefer it over
111154
# the executable in the MacOS directory.
112155
if os.path.isfile(INKSCAPE_APP):
113156
return INKSCAPE_APP
114-
if sys.platform == "win32":
115-
wr_handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
116-
try:
117-
rkey = winreg.OpenKey(wr_handle, "SOFTWARE\\Classes\\inkscape.svg\\DefaultIcon")
118-
inkscape = winreg.QueryValueEx(rkey, "")[0]
119-
except FileNotFoundError:
120-
msg = "Inkscape executable not found"
121-
raise FileNotFoundError(msg) from None
122-
return inkscape
123-
return "inkscape"
157+
158+
msg = "Inkscape executable not found in safe paths"
159+
raise FileNotFoundError(msg)
124160

125161
def convert_figure(self, data_format, data):
126162
"""
@@ -144,7 +180,7 @@ def convert_figure(self, data_format, data):
144180
else:
145181
# For backwards compatibility with specifying strings
146182
# Okay-ish, since the string is trusted
147-
full_cmd = self.command.format(*template_vars)
183+
full_cmd = self.command.format(**template_vars)
148184
subprocess.call(full_cmd, shell=isinstance(full_cmd, str)) # noqa: S603
149185

150186
# Read output from drive

0 commit comments

Comments
 (0)