From 77feb085064f0b5f4ed8c3a8448694d9ab263c89 Mon Sep 17 00:00:00 2001 From: garthoid Date: Fri, 15 May 2026 16:13:22 -0400 Subject: [PATCH] Use project discovery httpx --- server_api/ops/system_monitoring.py | 14 +++-- server_api/web_probe/httpx.py | 16 +++--- server_core/tool_binary_resolver.py | 82 +++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 server_core/tool_binary_resolver.py diff --git a/server_api/ops/system_monitoring.py b/server_api/ops/system_monitoring.py index c99e0818..4883834f 100644 --- a/server_api/ops/system_monitoring.py +++ b/server_api/ops/system_monitoring.py @@ -15,6 +15,7 @@ from server_core.command_executor import execute_command from server_core.modern_visual_engine import ModernVisualEngine from server_core.singletons import cache, telemetry +from server_core.tool_binary_resolver import resolve_projectdiscovery_httpx from server_core.tool_constants import ( BUILT_IN_TOOLS, REQUIRE_DPKG_CHECK, REQUIRE_GO_CHECK, REQUIRE_PIP_CHECK, @@ -74,11 +75,6 @@ def _probe_binary(check_type: str, binary: str) -> bool: binary — executable or package name to probe """ - home_path = os.path.expanduser("~") - paths_ovrrides = config_core.get("PATHS", {}) - GO_PATH = paths_ovrrides.get("GO_BINARYS", "{HOME}/go/bin/") - GO_BINARYS = GO_PATH.replace("{HOME}", home_path) - if check_type == "builtin": return True try: @@ -107,11 +103,13 @@ def _probe_binary(check_type: str, binary: str) -> bool: ) return binary in r.stdout elif check_type == "go": + if binary == "httpx": + return bool(resolve_projectdiscovery_httpx()) r = subprocess.run( - ["go", "version", "-m", GO_BINARYS], - stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, + ["which", binary], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) - return binary in r.stdout + return r.returncode == 0 else: # which (default) r = subprocess.run( ["which", binary], diff --git a/server_api/web_probe/httpx.py b/server_api/web_probe/httpx.py index 8db03664..1236f9c1 100644 --- a/server_api/web_probe/httpx.py +++ b/server_api/web_probe/httpx.py @@ -1,8 +1,7 @@ -import os from flask import Blueprint, request, jsonify import logging -from server_core import config_core from server_core.command_executor import execute_command +from server_core.tool_binary_resolver import resolve_projectdiscovery_httpx logger = logging.getLogger(__name__) @@ -13,14 +12,13 @@ def httpx(): """Execute httpx for fast HTTP probing and technology detection""" try: - - home_path = os.path.expanduser("~") - paths_ovrrides = config_core.get("PATHS", {}) - httpx_bin_template = paths_ovrrides.get("GO_BINARYS", "{HOME}/go/bin/") - httpx_bin = httpx_bin_template.replace("{HOME}", home_path) + "httpx" + httpx_bin = resolve_projectdiscovery_httpx() - if not os.path.isfile(httpx_bin) or not os.access(httpx_bin, os.X_OK): - logger.error(f"🚫 httpx binary not found or not executable at {httpx_bin}") + if not httpx_bin: + logger.error("🚫 ProjectDiscovery httpx binary was not found") + return jsonify({ + "error": "ProjectDiscovery httpx binary not found. Set PATHS.GO_BINARYS or install projectdiscovery/httpx." + }), 500 params = request.json target = params.get("target", "") diff --git a/server_core/tool_binary_resolver.py b/server_core/tool_binary_resolver.py new file mode 100644 index 00000000..4f7058e8 --- /dev/null +++ b/server_core/tool_binary_resolver.py @@ -0,0 +1,82 @@ +import os +import shutil +import subprocess +from typing import List, Optional + +from server_core import config_core + + +def _path_candidates(binary_name: str) -> List[str]: + """Build a de-duplicated list of executable path candidates.""" + home_path = os.path.expanduser("~") + paths_overrides = config_core.get("PATHS", {}) + go_bin_template = paths_overrides.get("GO_BINARYS", "{HOME}/go/bin/") + go_bin_dir = go_bin_template.replace("{HOME}", home_path) + configured_candidate = os.path.join(go_bin_dir, binary_name) + + candidates: List[str] = [configured_candidate] + + default_go_candidate = os.path.join(home_path, "go", "bin", binary_name) + candidates.append(default_go_candidate) + + resolved_from_path = shutil.which(binary_name) + if resolved_from_path: + candidates.append(resolved_from_path) + + for path_dir in os.get_exec_path(): + candidates.append(os.path.join(path_dir, binary_name)) + + unique_candidates: List[str] = [] + seen = set() + for candidate in candidates: + if candidate in seen: + continue + seen.add(candidate) + unique_candidates.append(candidate) + + return unique_candidates + + +def _is_executable(path: str) -> bool: + return os.path.isfile(path) and os.access(path, os.X_OK) + + +def _is_projectdiscovery_httpx(path: str) -> bool: + """Check whether a path is the ProjectDiscovery httpx binary.""" + if not _is_executable(path): + return False + + try: + probe = subprocess.run( + [path, "-h"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=3, + ) + except Exception: + return False + + output = (probe.stdout or "").lower() + markers = ("projectdiscovery", "-tech-detect", "-probe") + return any(marker in output for marker in markers) + + +def resolve_projectdiscovery_httpx() -> Optional[str]: + """Resolve a runnable ProjectDiscovery httpx binary path. + + Selection order: + 1) configured GO_BINARYS path, + 2) common Go path (~/go/bin), + 3) current PATH entries. + + Returns: + Absolute path to ProjectDiscovery httpx binary when found, else None. + """ + candidates = _path_candidates("httpx") + + for candidate in candidates: + if _is_projectdiscovery_httpx(candidate): + return candidate + + return None