From 0d2db1b6308cfe2beeaf40e9a8dcebb72dfe9dbd Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Mon, 3 Nov 2025 15:02:59 +0000 Subject: [PATCH 1/3] extract wheels directly from resource --- DSPythonNet3/DSPythonNet3Evaluator.cs | 50 ++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/DSPythonNet3/DSPythonNet3Evaluator.cs b/DSPythonNet3/DSPythonNet3Evaluator.cs index e57f073..c343e04 100644 --- a/DSPythonNet3/DSPythonNet3Evaluator.cs +++ b/DSPythonNet3/DSPythonNet3Evaluator.cs @@ -370,21 +370,55 @@ internal static async Task InstallPythonAsync() Assembly wheelsAssembly = context.LoadFromAssemblyPath(Path.Join(Path.GetDirectoryName(assembly.Location), "DSPythonNet3Wheels.dll")); + string sitePkgsPath = Path.Combine(Python.Included.Installer.EmbeddedPythonHome, "Lib", "site-packages"); + Directory.CreateDirectory(sitePkgsPath); + List pipWheelInstall = new List(); - await Task.WhenAll(wheelsAssembly.GetManifestResourceNames().Where(x => + + // Extract noo-pip wheels directly from the resource stream + foreach (var resName in wheelsAssembly.GetManifestResourceNames()) { - bool isWheel = x.Contains(".whl"); - if (isWheel && x.Contains("pywin32-")) + bool isWheel = resName.EndsWith(".whl"); + if (!isWheel) continue; + + if (resName.Contains("pywin32-")) { - pipWheelInstall.Add(x); - return false; + pipWheelInstall.Add(resName); + continue; } - return isWheel; - }).Select(wheel => Python.Included.Installer.InstallWheel(wheelsAssembly, wheel))).ConfigureAwait(false); + using (var stream = wheelsAssembly.GetManifestResourceStream(resName)) + { + if (stream == null || stream.Length == 0) + { + continue; + } + + using (var zip = new System.IO.Compression.ZipArchive(stream, System.IO.Compression.ZipArchiveMode.Read, false)) + { + foreach (var entry in zip.Entries) + { + if (string.IsNullOrEmpty(entry.Name)) continue; + + var destPath = Path.Combine(sitePkgsPath, entry.FullName.Replace('/', Path.DirectorySeparatorChar)); + var destDir = Path.GetDirectoryName(destPath); + if (!string.IsNullOrEmpty(destDir)) + { + Directory.CreateDirectory(destDir); + } + + using (var inStream = entry.Open()) + using (var outStream = new FileStream(destPath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + await inStream.CopyToAsync(outStream).ConfigureAwait(false); + } + } + } + } + } foreach (var pipWheelResource in pipWheelInstall) - { + { var pipWheelName = pipWheelResource.Remove(0, "DSPythonNet3Wheels.Resources.".Count()); string wheelPath = Path.Combine(Python.Included.Installer.EmbeddedPythonHome, "Lib", pipWheelName); using (Stream? stream = wheelsAssembly.GetManifestResourceStream(pipWheelResource)) From 338896cb8a30165e0de321ef08f8f23481c388fd Mon Sep 17 00:00:00 2001 From: Trygve Wastvedt Date: Mon, 3 Nov 2025 10:36:46 -0600 Subject: [PATCH 2/3] Update DSPythonNet3/DSPythonNet3Evaluator.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DSPythonNet3/DSPythonNet3Evaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DSPythonNet3/DSPythonNet3Evaluator.cs b/DSPythonNet3/DSPythonNet3Evaluator.cs index c343e04..9536a50 100644 --- a/DSPythonNet3/DSPythonNet3Evaluator.cs +++ b/DSPythonNet3/DSPythonNet3Evaluator.cs @@ -375,7 +375,7 @@ internal static async Task InstallPythonAsync() List pipWheelInstall = new List(); - // Extract noo-pip wheels directly from the resource stream + // Extract non-pip wheels directly from the resource stream foreach (var resName in wheelsAssembly.GetManifestResourceNames()) { bool isWheel = resName.EndsWith(".whl"); From 017611769753a7f4f226a85f0a8bf6b33a19a6c6 Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Mon, 3 Nov 2025 19:13:33 +0000 Subject: [PATCH 3/3] validate entry.FullName --- DSPythonNet3/DSPythonNet3Evaluator.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/DSPythonNet3/DSPythonNet3Evaluator.cs b/DSPythonNet3/DSPythonNet3Evaluator.cs index c343e04..fd29bd5 100644 --- a/DSPythonNet3/DSPythonNet3Evaluator.cs +++ b/DSPythonNet3/DSPythonNet3Evaluator.cs @@ -351,6 +351,8 @@ public static object EvaluatePythonScript( private static bool isPythonInstalled = false; /// /// Makes sure Python is installed on the system and its location added to the path. + /// Extracts non-pip wheels directly from the embedded resource stream into site-packages, + /// adds a zip-slip guard (normalize & validate paths), and uses pip only for pywin32. /// NOTE: Calling SetupPython multiple times will add the install location to the path many times, /// potentially causing the environment variable to overflow. /// @@ -370,12 +372,14 @@ internal static async Task InstallPythonAsync() Assembly wheelsAssembly = context.LoadFromAssemblyPath(Path.Join(Path.GetDirectoryName(assembly.Location), "DSPythonNet3Wheels.dll")); - string sitePkgsPath = Path.Combine(Python.Included.Installer.EmbeddedPythonHome, "Lib", "site-packages"); - Directory.CreateDirectory(sitePkgsPath); + string sitePkgs = Path.Combine(Python.Included.Installer.EmbeddedPythonHome, "Lib", "site-packages"); + var normalizedBase = Path.GetFullPath(sitePkgs) + Path.DirectorySeparatorChar; + + Directory.CreateDirectory(sitePkgs); List pipWheelInstall = new List(); - // Extract noo-pip wheels directly from the resource stream + // Extract non-pip wheels directly from the resource stream foreach (var resName in wheelsAssembly.GetManifestResourceNames()) { bool isWheel = resName.EndsWith(".whl"); @@ -400,7 +404,16 @@ internal static async Task InstallPythonAsync() { if (string.IsNullOrEmpty(entry.Name)) continue; - var destPath = Path.Combine(sitePkgsPath, entry.FullName.Replace('/', Path.DirectorySeparatorChar)); + var relPath = entry.FullName.Replace('/', Path.DirectorySeparatorChar); + var tentative = Path.Combine(sitePkgs, relPath); + var destPath = Path.GetFullPath(tentative); + + if (!destPath.StartsWith(normalizedBase, StringComparison.OrdinalIgnoreCase)) + { + dynamoLogger?.LogWarning($"[PyInit] Skipped suspicious wheel entry: {entry.FullName}", WarningLevel.Mild); + continue; + } + var destDir = Path.GetDirectoryName(destPath); if (!string.IsNullOrEmpty(destDir)) {