Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions server_api/ops/system_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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],
Expand Down
16 changes: 7 additions & 9 deletions server_api/web_probe/httpx.py
Original file line number Diff line number Diff line change
@@ -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__)

Expand All @@ -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", "")
Expand Down
82 changes: 82 additions & 0 deletions server_core/tool_binary_resolver.py
Original file line number Diff line number Diff line change
@@ -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