From 31879c01804163bca01f6b6ee4a155f705440902 Mon Sep 17 00:00:00 2001 From: mkultraWasHere Date: Tue, 14 Jul 2026 16:14:24 -0400 Subject: [PATCH 1/2] fix(network-ops): prefer site-packages scripts over PATH pip wrappers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of persistent impacket errors: _get_impacket_script_path() found pip entry point wrappers at ~/.local/bin/ (installed for Python 3.13) before checking site-packages. These wrappers pass _is_python_script() but fail when run with sys.executable (runtime Python 3.12) because they can't import from 3.13's site-packages. Fix: check site-packages/impacket/examples/ FIRST — these scripts are guaranteed to work with sys.executable since they live in the same Python installation that _ensure_impacket_installed() targets. Co-Authored-By: Claude Opus 4.6 (1M context) --- capabilities/network-ops/tools/impacket.py | 48 ++++++++++++---------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/capabilities/network-ops/tools/impacket.py b/capabilities/network-ops/tools/impacket.py index f3bd0c2..2c98612 100644 --- a/capabilities/network-ops/tools/impacket.py +++ b/capabilities/network-ops/tools/impacket.py @@ -112,13 +112,34 @@ def _get_impacket_script_path() -> Path: Returns a directory containing real Python impacket scripts (not shell wrappers) so they can be invoked via sys.executable. - Tries multiple common locations: - 1. Global PATH: resolve the found script's directory, following + Tries multiple common locations, preferring site-packages (which + matches sys.executable) over PATH (which may contain pip entry + point wrappers installed for a different Python version): + 1. pip-installed: site-packages/impacket/examples/ (same Python as sys.executable) + 2. apt-installed: /usr/share/doc/python3-impacket/examples/ + 3. Global PATH: resolve the found script's directory, following shell wrappers to the real scripts if needed - 2. pip-installed: site-packages/impacket/examples/ - 3. apt-installed: /usr/share/doc/python3-impacket/examples/ """ - # Check if scripts are on PATH (pipx / manual install) + # Prefer site-packages — these scripts are guaranteed to work with + # sys.executable since they live in the same Python installation. + # PATH entries (e.g. ~/.local/bin/) are pip entry point wrappers + # that may be installed for a different Python version. + try: + import impacket + + impacket_pkg_path = Path(impacket.__file__).parent + examples_path = impacket_pkg_path / "examples" + if examples_path.exists() and (examples_path / "secretsdump.py").exists(): + return examples_path + except ImportError: + pass + + # Fall back to apt installation path + apt_path = Path("/usr/share/doc/python3-impacket/examples/") + if apt_path.exists() and (apt_path / "secretsdump.py").exists(): + return apt_path + + # Last resort: check PATH (pipx / manual install) found = shutil.which("secretsdump.py") if found is not None: found_path = Path(found).resolve() @@ -138,23 +159,6 @@ def _get_impacket_script_path() -> Path: f"Impacket script at {found_path} is a shell wrapper, skipping PATH discovery" ) - # Try to find impacket in site-packages (pip install) - try: - import impacket - - impacket_pkg_path = Path(impacket.__file__).parent - examples_path = impacket_pkg_path / "examples" - # Check that actual script files exist, not just the directory - if examples_path.exists() and (examples_path / "secretsdump.py").exists(): - return examples_path - except ImportError: - pass - - # Fall back to apt installation path - apt_path = Path("/usr/share/doc/python3-impacket/examples/") - if apt_path.exists() and (apt_path / "secretsdump.py").exists(): - return apt_path - # Default fallback return Path("/usr/share/doc/python3-impacket/examples/") From 19b1c213d3ada352ab697c1eb4ec262b21c371ae Mon Sep 17 00:00:00 2001 From: mkultraWasHere Date: Tue, 14 Jul 2026 16:18:03 -0400 Subject: [PATCH 2/2] fix(network-ops): check subprocess impacket import, not in-process The in-process `import impacket` can succeed via inherited PYTHONPATH or global site-packages while sys.executable subprocesses fail because they run in an isolated uv venv. Now checks importability via subprocess (matching how _build_script_command runs scripts) and also reorders script path resolution to prefer site-packages over PATH pip wrappers installed for a different Python version. Co-Authored-By: Claude Opus 4.6 (1M context) --- capabilities/network-ops/tools/impacket.py | 77 +++++++++++++--------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/capabilities/network-ops/tools/impacket.py b/capabilities/network-ops/tools/impacket.py index 2c98612..8bbee03 100644 --- a/capabilities/network-ops/tools/impacket.py +++ b/capabilities/network-ops/tools/impacket.py @@ -61,45 +61,62 @@ def _extract_real_path_from_wrapper(wrapper_path: Path) -> Path | None: def _ensure_impacket_installed() -> None: - """Install impacket into the running Python if it's not importable. + """Ensure impacket is importable by subprocesses using sys.executable. - The runtime may not process ``dependencies.python`` from - ``capability.yaml``, so we do a best-effort pip install at import - time as a fallback. + The in-process ``import impacket`` may succeed (via inherited + PYTHONPATH or global site-packages) while subprocesses spawned with + ``sys.executable`` fail because they run in an isolated venv. We + check the *subprocess* environment specifically, since that's what + ``_build_script_command`` uses to run impacket scripts. Set ``DREADNODE_SKIP_AUTO_INSTALL=1`` to disable (useful in tests/CI). """ + import subprocess + if os.environ.get("DREADNODE_SKIP_AUTO_INSTALL", "").strip() in ("1", "true", "yes"): return + # Check if sys.executable can import impacket as a subprocess — + # this matches how _build_script_command actually runs scripts. try: - import impacket as _ # noqa: F401 - except ImportError: - import subprocess + subprocess.run( + [sys.executable, "-c", "import impacket"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + timeout=10, + ) + return # sys.executable can import impacket — nothing to do + except (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError): + pass - logger.warning("impacket not importable — attempting pip install") - for extra_args in ([], ["--break-system-packages"]): - try: - subprocess.run( - [ - sys.executable, - "-m", - "pip", - "install", - "--quiet", - "impacket>=0.12.0", - *extra_args, - ], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - check=True, - timeout=120, - ) - logger.info("impacket installed successfully") - return - except (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError): - continue - logger.error("Failed to install impacket — impacket tools will not work") + logger.warning( + f"impacket not importable by {sys.executable} — attempting pip install" + ) + for extra_args in ([], ["--break-system-packages"]): + try: + subprocess.run( + [ + sys.executable, + "-m", + "pip", + "install", + "--quiet", + "impacket>=0.12.0", + *extra_args, + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + timeout=120, + ) + logger.info("impacket installed successfully") + return + except (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError): + continue + logger.error( + f"Failed to install impacket into {sys.executable} — impacket tools will not work" + ) _ensure_impacket_installed()