99import os
1010import subprocess
1111import sys
12+ import warnings
13+ from pathlib import Path
1214from shutil import which
1315from 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