feat(security-sidecar): implement a security tooling sidecar with tiered installer and protocol

- Add `zesdex_sec_daemon` module with main entry point for running the security daemon.
- Implement `TieredInstaller` for installing security tools from various sources (pip, binaries, gems).
- Create a newline-delimited JSON frame protocol for communication between the daemon and tools.
- Introduce a `ToolRegistry` for managing and dispatching tool executions.
- Add various tools including HTTP, SQLMap, Nuclei, and more with their respective execution logic.
- Establish health check and installation commands for tool management.
- Include prompts for classifier and quality reviewer to enhance code review and safety checks.
- Document the system's tools and guidelines for usage.
This commit is contained in:
asepharyana
2026-07-11 21:25:30 +07:00
parent 2ded2d8bf1
commit f6389018f5
28 changed files with 1635 additions and 200 deletions
+6
View File
@@ -0,0 +1,6 @@
requests>=2.31.0
# Optional extras (install with: pip install zesdex-security-daemon[full])
# pycryptodome>=3.20.0
# factordb-python>=2.0.0
# pwntools>=4.12.0
# ropper>=1.13.0
+29
View File
@@ -0,0 +1,29 @@
[metadata]
name = zesdex-security-daemon
version = 0.1.0
description = Security tooling sidecar for zesdex — authorized pentesting/CTF/research tool dispatch
author = asepharyana
author_email = superaseph@gmail.com
[options]
packages = zesdex_sec_daemon
install_requires =
requests>=2.31.0
[options.extras_require]
web =
sqlmap>=1.8.0
python-nmap>=0.7.1
crypto =
pycryptodome>=3.20.0
factordb-python>=2.0.0
re =
wasm-decompile>=0.5.0
pwn =
pwntools>=4.12.0
ropper>=1.13.0
full =
%(web)s
%(crypto)s
%(re)s
%(pwn)s
@@ -0,0 +1,17 @@
"""zesdex-security-daemon: authorized security tooling sidecar.
Newline-delimited JSON frame protocol over stdin/stdout.
Single-threaded serialized dispatch with wall-clock timeout.
"""
from .protocol import run_daemon, SecProtocolError
from .tools import ToolRegistry, ToolResult
from .installer import TieredInstaller
__all__ = [
"run_daemon",
"SecProtocolError",
"ToolRegistry",
"ToolResult",
"TieredInstaller",
]
@@ -0,0 +1,25 @@
"""Entry point: run the security daemon reading JSON frames from stdin."""
import sys
from .protocol import run_daemon
def main():
if "--install" in sys.argv:
from .installer import TieredInstaller
installer = TieredInstaller()
result = installer.install_all()
print(result.model_dump_json())
return
if "--health" in sys.argv:
from .tools import ToolRegistry
registry = ToolRegistry()
health = registry.health_check()
import json
print(json.dumps(health))
return
run_daemon(sys.stdin, sys.stdout)
if __name__ == "__main__":
main()
@@ -0,0 +1,241 @@
"""Tiered installer for security sidecar tools.
Installation tiers:
pip — Python packages (pycryptodome, pwntools, factordb-python, etc.)
binary — Pre-built GitHub release binaries (nuclei, ffuf, dalfox, etc.)
gem — Ruby gems (zap-cli, etc.)
detect — Manual-detect-only (z3, sage, etc. — user must install themselves)
"""
import json
import os
import platform
import shutil
import subprocess
import sys
import tarfile
import tempfile
import urllib.request
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
@dataclass
class InstallResult:
ok: bool = False
message: str = ""
installed: list[str] = field(default_factory=list)
failed: list[str] = field(default_factory=list)
skipped: list[str] = field(default_factory=list)
class TieredInstaller:
"""Multi-tier installer for security tools.
Detects the current platform and tries each tier in order of
preference: pip -> binary -> gem -> detect (manual only).
"""
def __init__(self, target_dir: Optional[str] = None):
self.target_dir = Path(target_dir or self._default_bin_dir())
self.target_dir.mkdir(parents=True, exist_ok=True)
self._pip_available = shutil.which("pip3") is not None or shutil.which("pip") is not None
self._gem_available = shutil.which("gem") is not None
self._arch = platform.machine()
self._os = platform.system().lower()
def _default_bin_dir(self) -> str:
if self._os == "linux":
return "/usr/local/bin"
return os.path.expanduser("~/.local/bin")
# ── pip tier ────────────────────────────────────────────────────
def _pip_install(self, pkg: str) -> bool:
pip = shutil.which("pip3") or shutil.which("pip")
if not pip:
return False
try:
result = subprocess.run(
[pip, "install", "--quiet", pkg],
capture_output=True, text=True, timeout=120,
)
return result.returncode == 0
except subprocess.TimeoutExpired:
return False
except Exception:
return False
# ── GitHub release binary tier ──────────────────────────────────
def _download_gh_release(
self, repo: str, asset_pattern: str,
) -> Optional[Path]:
api_url = f"https://api.github.com/repos/{repo}/releases/latest"
try:
req = urllib.request.Request(api_url, headers={
"Accept": "application/json",
"User-Agent": "zesdex-security-daemon/0.1.0",
})
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.loads(resp.read().decode())
assets = data.get("assets", [])
os_arch_tag = f"{self._os}_{self._arch}"
for asset in assets:
name = asset["name"]
if asset_pattern.replace("{os_arch}", os_arch_tag) in name:
download_url = asset["browser_download_url"]
break
else:
# Try without os_arch matching, just look for the pattern.
candidates = [a for a in assets if asset_pattern.split("/")[0] in a["name"]]
if not candidates:
return None
download_url = candidates[0]["browser_download_url"]
with tempfile.NamedTemporaryFile(suffix=".tmp", delete=False) as tmp:
with urllib.request.urlopen(download_url, timeout=120) as dl:
tmp.write(dl.read())
tmppath = tmp.name
if download_url.endswith(".tar.gz"):
extract_dir = tempfile.mkdtemp()
with tarfile.open(tmppath, "r:gz") as tar:
tar.extractall(path=extract_dir)
os.unlink(tmppath)
# Find the binary in extracted files.
for root, _dirs, files in os.walk(extract_dir):
for fname in files:
if fname == asset_pattern.split("/")[0].replace(".tar.gz", ""):
return Path(root) / fname
return None
else:
result = Path(tmppath)
result.chmod(0o755)
return result
except Exception:
return None
def _install_binary(self, name: str, repo: str, asset_pattern: str) -> bool:
downloaded = self._download_gh_release(repo, asset_pattern)
if downloaded is None:
return False
dest = self.target_dir / name
try:
shutil.move(str(downloaded), str(dest))
dest.chmod(0o755)
return True
except Exception:
return False
# ── Gem tier ────────────────────────────────────────────────────
def _gem_install(self, gem_name: str) -> bool:
if not self._gem_available:
return False
try:
result = subprocess.run(
["gem", "install", "--quiet", gem_name],
capture_output=True, text=True, timeout=120,
)
return result.returncode == 0
except Exception:
return False
# ── Tool-specific installers ────────────────────────────────────
def _install_pip_tools(self) -> list[str]:
pkgs = [
"pycryptodome",
"pwntools",
"requests",
"factordb-python",
]
installed = []
for pkg in pkgs:
if self._pip_install(pkg):
installed.append(pkg)
return installed
def _install_binary_tools(self) -> list[str]:
targets = [
("nuclei", "projectdiscovery/nuclei", "nuclei_{os_arch}.tar.gz"),
("ffuf", "ffuf/ffuf", "ffuf_{os_arch}.tar.gz"),
("dalfox", "hahwul/dalfox", "dalfox_{os_arch}.tar.gz"),
("httpx", "projectdiscovery/httpx", "httpx_{os_arch}.tar.gz"),
]
installed = []
for name, repo, pattern in targets:
if shutil.which(name) is not None:
installed.append(name)
continue
if self._install_binary(name, repo, pattern):
installed.append(name)
return installed
def _install_gem_tools(self) -> list[str]:
installed = []
if self._gem_install("zap-cli"):
installed.append("zap-cli")
return installed
# ── Public API ──────────────────────────────────────────────────
def install_all(self) -> InstallResult:
result = InstallResult()
try:
pip_ok = self._install_pip_tools()
result.installed.extend(pip_ok)
except Exception as e:
result.failed.append(f"pip: {e}")
try:
bin_ok = self._install_binary_tools()
result.installed.extend(bin_ok)
except Exception as e:
result.failed.append(f"binary: {e}")
try:
gem_ok = self._install_gem_tools()
result.installed.extend(gem_ok)
except Exception as e:
result.failed.append(f"gem: {e}")
result.ok = True
result.message = (
f"Installed {len(result.installed)} tool(s): "
f"{', '.join(result.installed)}"
)
return result
def install_tool(self, tool_name: str) -> dict:
"""Install a single tool by name. Returns {"ok": bool, "message": str}."""
pip_map = {
"pycryptodome": "pycryptodome",
"pwntools": "pwntools",
"factordb": "factordb-python",
"requests": "requests",
}
binary_map = {
"nuclei": ("projectdiscovery/nuclei", "nuclei_{os_arch}.tar.gz"),
"ffuf": ("ffuf/ffuf", "ffuf_{os_arch}.tar.gz"),
"dalfox": ("hahwul/dalfox", "dalfox_{os_arch}.tar.gz"),
"httpx": ("projectdiscovery/httpx", "httpx_{os_arch}.tar.gz"),
"sqlmap": ("sqlmapproject/sqlmap", "sqlmap.tar.gz"),
}
gem_map = {
"zap-cli": "zap-cli",
}
if tool_name in pip_map:
ok = self._pip_install(pip_map[tool_name])
return {"ok": ok, "message": f"pip install {pip_map[tool_name]}: {'ok' if ok else 'failed'}"}
if tool_name in binary_map:
repo, pattern = binary_map[tool_name]
ok = self._install_binary(tool_name, repo, pattern)
return {"ok": ok, "message": f"binary install {tool_name}: {'ok' if ok else 'failed'}"}
if tool_name in gem_map:
ok = self._gem_install(gem_map[tool_name])
return {"ok": ok, "message": f"gem install {gem_map[tool_name]}: {'ok' if ok else 'failed'}"}
return {"ok": False, "message": f"no installer available for {tool_name}; may need manual install"}
@@ -0,0 +1,173 @@
"""Newline-delimited JSON frame protocol for the security sidecar.
Frame format: one JSON object per line, terminated by LF.
Request: {"id": "<req_id>", "op": "call"|"health"|"install", "tool": "<name>", "args": {...}, "timeout": <ms>}
Response: {"id": "<req_id>", "ok": true, "output": "..."} | {"id": "<req_id>", "ok": false, "error": "..."}
"""
import json
import sys
import traceback
from typing import TextIO, Optional
from dataclasses import dataclass, field
from datetime import datetime, timezone
class SecProtocolError(Exception):
"""Raised on malformed frames or protocol violations."""
@dataclass
class SecRequest:
req_id: str
op: str # "call" | "health" | "install"
tool: str = ""
args: dict = field(default_factory=dict)
timeout_ms: int = 30_000
@classmethod
def parse(cls, line: str) -> "SecRequest":
line = line.strip()
if not line:
raise SecProtocolError("empty line")
try:
data = json.loads(line)
except json.JSONDecodeError as e:
raise SecProtocolError(f"invalid JSON: {e}") from e
req_id = data.get("id")
op = data.get("op")
if not isinstance(req_id, str) or not req_id:
raise SecProtocolError("missing or invalid 'id'")
if op not in ("call", "health", "install"):
raise SecProtocolError(f"unknown op: {op!r}")
return cls(
req_id=req_id,
op=op,
tool=data.get("tool", ""),
args=data.get("args", {}),
timeout_ms=data.get("timeout", 30_000),
)
@dataclass
class SecResponse:
req_id: str
ok: bool
output: str = ""
error: str = ""
duration_ms: int = 0
def to_json(self) -> str:
obj = {"id": self.req_id, "ok": self.ok, "ts": datetime.now(timezone.utc).isoformat()}
if self.ok:
obj["output"] = self.output
else:
obj["error"] = self.error
obj["duration_ms"] = self.duration_ms
return json.dumps(obj, ensure_ascii=False)
class FrameReader:
"""Reads one JSON line from a buffered stream, enforcing a 64 MiB hard cap."""
MAX_FRAME_BYTES = 64 * 1024 * 1024
def __init__(self, stream: TextIO):
self._stream = stream
def read_line(self) -> Optional[str]:
line = self._stream.readline()
if not line:
return None
if len(line) > self.MAX_FRAME_BYTES:
raise SecProtocolError(f"frame exceeds {self.MAX_FRAME_BYTES} byte limit")
return line
def make_handshake_frame(token: str) -> str:
return json.dumps({"op": "handshake", "token": token})
def verify_handshake(line: str, expected_token: str) -> bool:
try:
data = json.loads(line.strip())
return data.get("op") == "handshake" and data.get("token") == expected_token
except (json.JSONDecodeError, KeyError):
return False
def run_daemon(stdin: TextIO, stdout: TextIO, token: str = "", registry=None, timeout_cap_ms: int = 300_000):
"""Read requests from stdin, dispatch to tool registry, write responses to stdout."""
from .tools import ToolRegistry
registry = registry or ToolRegistry()
reader = FrameReader(stdin)
first = reader.read_line()
if first is None:
return
if token:
if not verify_handshake(first, token):
err = json.dumps({"ok": False, "error": "handshake failed"})
stdout.write(err + "\n")
stdout.flush()
return
else:
try:
req = SecRequest.parse(first)
_handle_request(req, registry, stdout, timeout_cap_ms)
except SecProtocolError as e:
_write_error("init", str(e), stdout)
while True:
line = reader.read_line()
if line is None:
break
if not line.strip():
continue
try:
req = SecRequest.parse(line)
except SecProtocolError as e:
_write_error("unknown", str(e), stdout)
continue
_handle_request(req, registry, stdout, timeout_cap_ms)
def _handle_request(req: SecRequest, registry, stdout: TextIO, global_cap_ms: int):
from .tools import ToolResult
start = datetime.now(timezone.utc)
effective_timeout = min(req.timeout_ms, global_cap_ms)
try:
if req.op == "health":
health = registry.health_check()
resp = SecResponse(req_id=req.req_id, ok=True, output=json.dumps(health))
elif req.op == "install":
installer_cls = None
try:
from .installer import TieredInstaller
installer_cls = TieredInstaller
except ImportError:
pass
if installer_cls:
installer = installer_cls()
result = installer.install_tool(req.tool)
resp = SecResponse(req_id=req.req_id, ok=result["ok"], output=result.get("message", ""))
else:
resp = SecResponse(req_id=req.req_id, ok=False, error="installer not available")
else:
result: ToolResult = registry.run(req.tool, req.args, timeout_ms=effective_timeout)
if result.ok:
resp = SecResponse(req_id=req.req_id, ok=True, output=result.output)
else:
resp = SecResponse(req_id=req.req_id, ok=False, error=result.error)
except Exception as exc:
resp = SecResponse(req_id=req.req_id, ok=False, error=f"dispatch error: {exc}")
traceback.print_exc(file=sys.stderr)
elapsed = int((datetime.now(timezone.utc) - start).total_seconds() * 1000)
resp.duration_ms = elapsed
stdout.write(resp.to_json() + "\n")
stdout.flush()
def _write_error(req_id: str, msg: str, stdout: TextIO):
resp = SecResponse(req_id=req_id, ok=False, error=msg)
stdout.write(resp.to_json() + "\n")
stdout.flush()
+602
View File
@@ -0,0 +1,602 @@
"""Tool registry and dispatch for the security sidecar.
Each tool is a callable(subprocess_args, timeout_ms) -> ToolResult.
The registry maps tool names to implementations and provides health checking.
"""
import json
import os
import shlex
import shutil
import signal
import subprocess
import sys
import tempfile
import threading
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
@dataclass
class ToolResult:
ok: bool
output: str = ""
error: str = ""
returncode: int = -1
timed_out: bool = False
duration_ms: int = 0
def _run_subprocess(
cmd: list[str],
stdin_data: Optional[bytes] = None,
timeout_ms: int = 30_000,
cwd: Optional[Path] = None,
env: Optional[dict[str, str]] = None,
) -> ToolResult:
start = datetime.now(timezone.utc)
try:
proc = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=cwd,
env=env,
preexec_fn=lambda: signal.signal(signal.SIGXCPU, signal.SIG_DFL),
)
stdout_b, stderr_b = b"", b""
done = threading.Event()
def _reader():
nonlocal stdout_b, stderr_b
try:
stdout_b, stderr_b = proc.communicate(input=stdin_data, timeout=timeout_ms / 1000)
except subprocess.TimeoutExpired:
proc.kill()
stdout_b, stderr_b = proc.communicate()
finally:
done.set()
reader_thread = threading.Thread(target=_reader, daemon=True)
reader_thread.start()
reader_thread.join(timeout=(timeout_ms / 1000) + 2)
if not done.is_set():
proc.kill()
reader_thread.join(1)
elapsed = int((datetime.now(timezone.utc) - start).total_seconds() * 1000)
return ToolResult(
ok=False,
error=f"timed out after {timeout_ms}ms",
returncode=-signal.SIGKILL,
timed_out=True,
duration_ms=elapsed,
)
combined = stdout_b.decode("utf-8", errors="replace")
if stderr_b:
combined += "\n" + stderr_b.decode("utf-8", errors="replace")
elapsed = int((datetime.now(timezone.utc) - start).total_seconds() * 1000)
return ToolResult(
ok=proc.returncode == 0,
output=combined if proc.returncode == 0 else "",
error=combined if proc.returncode != 0 else "",
returncode=proc.returncode or 0,
duration_ms=elapsed,
)
except FileNotFoundError:
elapsed = int((datetime.now(timezone.utc) - start).total_seconds() * 1000)
return ToolResult(
ok=False,
error=f"executable not found: {cmd[0]}",
duration_ms=elapsed,
)
except Exception as e:
elapsed = int((datetime.now(timezone.utc) - start).total_seconds() * 1000)
return ToolResult(ok=False, error=str(e), duration_ms=elapsed)
def _check_tool(name: str) -> bool:
return shutil.which(name) is not None
# ── Web tools ──────────────────────────────────────────────────────
def _run_http(args: dict, timeout_ms: int) -> ToolResult:
url = args.get("url", "")
method = args.get("method", "GET").upper()
headers = args.get("headers", {})
data = args.get("data", "")
if not url:
return ToolResult(ok=False, error="url is required")
cmd = ["curl", "-s", "-S", "-L", "-X", method]
for k, v in headers.items():
cmd.extend(["-H", f"{k}: {v}"])
if data and method in ("POST", "PUT", "PATCH"):
cmd.extend(["-d", data])
cmd.append(url)
return _run_subprocess(cmd, timeout_ms=timeout_ms)
def _run_sqlmap(args: dict, timeout_ms: int) -> ToolResult:
url = args.get("url", "")
if not url:
return ToolResult(ok=False, error="url is required")
cmd = ["sqlmap", "--batch", "--random-agent", "--time-sec", "5"]
if args.get("cookie"):
cmd.extend(["--cookie", args["cookie"]])
if args.get("data"):
cmd.extend(["--data", args["data"]])
if args.get("level"):
cmd.extend(["--level", str(args["level"])])
if args.get("risk"):
cmd.extend(["--risk", str(args["risk"])])
cmd.append(url)
return _run_subprocess(cmd, timeout_ms=timeout_ms)
def _run_nuclei(args: dict, timeout_ms: int) -> ToolResult:
target = args.get("target", "")
if not target:
return ToolResult(ok=False, error="target is required")
cmd = ["nuclei", "-silent", "-no-color"]
if args.get("templates"):
cmd.extend(["-t", args["templates"]])
if args.get("severity"):
cmd.extend(["-severity", args["severity"]])
cmd.extend(["-u", target])
return _run_subprocess(cmd, timeout_ms=timeout_ms)
def _run_ffuf(args: dict, timeout_ms: int) -> ToolResult:
url = args.get("url", "")
wordlist = args.get("wordlist", "/usr/share/wordlists/dirb/common.txt")
if not url:
return ToolResult(ok=False, error="url is required")
cmd = ["ffuf", "-u", url, "-w", wordlist, "-ac", "-t", "40"]
if args.get("extensions"):
cmd.extend(["-e", args["extensions"]])
if args.get("fc"):
cmd.extend(["-fc", str(args["fc"])])
return _run_subprocess(cmd, timeout_ms=timeout_ms)
def _run_dalfox(args: dict, timeout_ms: int) -> ToolResult:
url = args.get("url", "")
if not url:
return ToolResult(ok=False, error="url is required")
cmd = ["dalfox", "url", url, "--silence", "--no-color", "--only-poc", "gfm"]
if args.get("cookie"):
cmd.extend(["--cookie", args["cookie"]])
if args.get("param"):
cmd.extend(["-p", args["param"]])
return _run_subprocess(cmd, timeout_ms=timeout_ms)
def _run_zap(args: dict, timeout_ms: int) -> ToolResult:
target = args.get("target", "")
if not target:
return ToolResult(ok=False, error="target is required")
cmd = ["zap-cli", "--silent", "quick-scan", "-t", str(args.get("timeout", 60))]
if args.get("spider"):
cmd.append("--spider")
cmd.append(target)
return _run_subprocess(cmd, timeout_ms=timeout_ms)
def _run_xss_confirm(args: dict, timeout_ms: int) -> ToolResult:
payloads = args.get("payloads", [
"<script>alert(1)</script>",
"\"><script>alert(1)</script>",
"';alert(1)//",
])
url_template = args.get("url", "")
param = args.get("param", "q")
if not url_template:
return ToolResult(ok=False, error="url template with {payload} placeholder is required")
for payload in payloads:
url = url_template.replace("{payload}", payload)
try:
resp = _run_subprocess(
["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", url],
timeout_ms=timeout_ms // len(payloads),
)
if resp.ok and resp.output.strip() not in ("404", "400"):
return ToolResult(
ok=True,
output=f"XSS vector confirmed: {payload} returned HTTP {resp.output.strip()}",
)
except Exception:
continue
return ToolResult(ok=False, error="no XSS vectors confirmed from payload set")
# ── Crypto tools ───────────────────────────────────────────────────
def _run_z3(args: dict, timeout_ms: int) -> ToolResult:
script = args.get("script", "")
if not script:
return ToolResult(ok=False, error="z3 script (SMT-LIB or Python) is required")
ext = ".smt2" if script.strip().startswith("(") else ".py"
with tempfile.NamedTemporaryFile(
mode="w", suffix=ext, delete=False, prefix="z3_"
) as f:
f.write(script)
tmp = f.name
try:
if ext == ".py":
cmd = ["python3", tmp]
else:
cmd = ["z3", "-in", tmp]
result = _run_subprocess(cmd, timeout_ms=timeout_ms)
os.unlink(tmp)
return result
except Exception:
if os.path.exists(tmp):
os.unlink(tmp)
raise
def _run_sage(args: dict, timeout_ms: int) -> ToolResult:
script = args.get("script", "")
if not script:
return ToolResult(ok=False, error="sage script is required")
with tempfile.NamedTemporaryFile(
mode="w", suffix=".sage", delete=False, prefix="sage_"
) as f:
f.write(script)
tmp = f.name
try:
result = _run_subprocess(["sage", tmp], timeout_ms=timeout_ms)
os.unlink(tmp)
return result
except Exception:
if os.path.exists(tmp):
os.unlink(tmp)
raise
def _run_rsa_tool(args: dict, timeout_ms: int) -> ToolResult:
n = args.get("n", "")
e = args.get("e", "65537")
if not n:
return ToolResult(ok=False, error="n (modulus) is required")
script = f"""
import sys
try:
from factordb.factordb import FactorDB
n = int("{n}")
f = FactorDB(n)
f.connect()
factors = f.get_factor_list()
if factors:
print("factors:", factors)
else:
print("no factors found from FactorDB")
except ImportError:
print("factordb-python not available, trying z3...")
except Exception as e:
print(f"error: {{e}}")
"""
return _run_subprocess(
["python3", "-c", script.replace("{", "{{").replace("}", "}}")],
timeout_ms=timeout_ms,
)
def _run_factordb(args: dict, timeout_ms: int) -> ToolResult:
n = args.get("n", "")
if not n:
return ToolResult(ok=False, error="n is required")
script = f"""
import sys, json
try:
from factordb.factordb import FactorDB
f = FactorDB({n})
f.connect()
factors = f.get_factor_list()
print(json.dumps({{"factors": factors, "status": f.get_status()}}))
except ImportError:
import urllib.request
url = f"http://factordb.com/api?query={n}"
resp = urllib.request.urlopen(url, timeout=10)
print(resp.read().decode())
except Exception as e:
print(json.dumps({{"error": str(e)}}))
"""
return _run_subprocess(["python3", "-c", script], timeout_ms=timeout_ms)
def _run_hashcat(args: dict, timeout_ms: int) -> ToolResult:
hash_value = args.get("hash", "")
mode = args.get("mode", "0")
wordlist = args.get("wordlist", "/usr/share/wordlists/rockyou.txt")
if not hash_value:
return ToolResult(ok=False, error="hash is required")
cmd = ["hashcat", "--force", "-m", mode, "-a", "0", hash_value, wordlist]
if args.get("rules"):
cmd.extend(["-r", args["rules"]])
return _run_subprocess(cmd, timeout_ms=timeout_ms)
def _run_hashid(args: dict, timeout_ms: int) -> ToolResult:
hash_value = args.get("hash", "")
if not hash_value:
return ToolResult(ok=False, error="hash is required")
cmd = ["hashid", "-m", hash_value]
return _run_subprocess(cmd, timeout_ms=timeout_ms)
def _run_decode(args: dict, timeout_ms: int) -> ToolResult:
encoded = args.get("data", "")
encoding = args.get("encoding", "base64")
if not encoded:
return ToolResult(ok=False, error="data is required")
script = f"""
import base64, binascii, json
data = {json.dumps(encoded)}
enc = {json.dumps(encoding)}
try:
if enc == "base64":
result = base64.b64decode(data).decode("utf-8", errors="replace")
elif enc == "base32":
result = base64.b32decode(data).decode("utf-8", errors="replace")
elif enc == "hex":
result = bytes.fromhex(data).decode("utf-8", errors="replace")
elif enc == "rot13":
import codecs
result = codecs.decode(data, "rot_13")
else:
result = f"unknown encoding: {{enc}}"
print(result)
except Exception as e:
print(f"decode failed: {{e}}")
"""
return _run_subprocess(["python3", "-c", script], timeout_ms=timeout_ms)
# ── Reverse-engineering tools ──────────────────────────────────────
def _run_js_deobf(args: dict, timeout_ms: int) -> ToolResult:
source = args.get("source", "")
if not source:
return ToolResult(ok=False, error="source is required")
with tempfile.NamedTemporaryFile(
mode="w", suffix=".js", delete=False, prefix="jsdeob_"
) as f:
f.write(source)
tmp = f.name
try:
cmd = ["npx", "--yes", "deobfuscate-js", tmp]
result = _run_subprocess(cmd, timeout_ms=timeout_ms)
os.unlink(tmp)
return result
except Exception:
if os.path.exists(tmp):
os.unlink(tmp)
raise
def _run_sourcemap(args: dict, timeout_ms: int) -> ToolResult:
url = args.get("url", "")
if not url:
return ToolResult(ok=False, error="url is required")
cmd = ["curl", "-s", "-L", url]
result = _run_subprocess(cmd, timeout_ms=timeout_ms)
if not result.ok:
return result
try:
import base64
import json
data = json.loads(result.output)
if "mappings" in data:
sources = data.get("sources", [])
return ToolResult(ok=True, output=json.dumps({"sources": sources, "file": data.get("file", "")}))
return ToolResult(ok=False, error="response is not a valid source map")
except json.JSONDecodeError as e:
return ToolResult(ok=False, error=f"invalid JSON: {e}")
def _run_wasm_decompile(args: dict, timeout_ms: int) -> ToolResult:
wasm_path = args.get("path", "")
wasm_data = args.get("data", "")
if wasm_path:
cmd = ["wasm-decompile", wasm_path]
elif wasm_data:
with tempfile.NamedTemporaryFile(
mode="wb", suffix=".wasm", delete=False, prefix="wasm_"
) as f:
import base64
f.write(base64.b64decode(wasm_data))
tmp = f.name
try:
cmd = ["wasm-decompile", tmp]
result = _run_subprocess(cmd, timeout_ms=timeout_ms)
os.unlink(tmp)
return result
except Exception:
if os.path.exists(tmp):
os.unlink(tmp)
raise
else:
return ToolResult(ok=False, error="path or base64-encoded data is required")
return _run_subprocess(cmd, timeout_ms=timeout_ms)
# ── Pwn tools ──────────────────────────────────────────────────────
def _run_triage(args: dict, timeout_ms: int) -> ToolResult:
binary = args.get("binary", "")
if not binary:
return ToolResult(ok=False, error="binary path is required")
cmd = ["file", binary]
file_result = _run_subprocess(cmd, timeout_ms=5000)
checksec_cmd = ["checksec", "--file=" + binary]
check_result = _run_subprocess(checksec_cmd, timeout_ms=5000)
combined = file_result.output or ""
if check_result.output:
combined += "\n" + check_result.output
return ToolResult(ok=True, output=combined)
def _run_ropgadget(args: dict, timeout_ms: int) -> ToolResult:
binary = args.get("binary", "")
if not binary:
return ToolResult(ok=False, error="binary path is required")
cmd = ["ROPgadget", "--binary", binary]
if args.get("depth"):
cmd.extend(["--depth", str(args["depth"])])
if args.get("only"):
cmd.extend(["--only", args["only"]])
if args.get("range"):
cmd.extend(["--range", args["range"]])
cmd.append("--silent")
return _run_subprocess(cmd, timeout_ms=timeout_ms)
def _run_pwntools(args: dict, timeout_ms: int) -> ToolResult:
script = args.get("script", "")
if not script:
return ToolResult(ok=False, error="pwntools Python script is required")
with tempfile.NamedTemporaryFile(
mode="w", suffix=".py", delete=False, prefix="pwn_"
) as f:
f.write("#!/usr/bin/env python3\n")
f.write("from pwn import *\n")
f.write("context.log_level = 'error'\n")
f.write(script)
tmp = f.name
try:
result = _run_subprocess(["python3", tmp], timeout_ms=timeout_ms)
os.unlink(tmp)
return result
except Exception:
if os.path.exists(tmp):
os.unlink(tmp)
raise
def _run_exploit_template(args: dict, timeout_ms: int) -> ToolResult:
target = args.get("target", "")
template_type = args.get("type", "ret2libc")
if not target:
return ToolResult(ok=False, error="target binary path is required")
arch = args.get("arch", "amd64")
template = f"""#!/usr/bin/env python3
from pwn import *
context.binary = '{target}'
context.arch = '{arch}'
context.log_level = 'warn'
elf = ELF('{target}')
"""
if template_type == "ret2libc":
template += f"""
# ret2libc template
rop = ROP(elf)
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])
if pop_rdi:
pop_rdi = pop_rdi[0]
print(f"pop rdi; ret @ {{hex(pop_rdi)}}")
bin_sh = next(elf.search(b'/bin/sh'), None)
if bin_sh:
print(f"/bin/sh @ {{hex(bin_sh)}}")
system = elf.plt.get('system')
if system:
print(f"system @ {{hex(system)}}")
else:
libc = elf.libc
if libc:
print(f"libc: {{libc.path}}")
"""
elif template_type == "shellcode":
template += f"""
# shellcode execution template
shellcode = asm(shellcraft.sh())
print(f"shellcode ({len(shellcode)} bytes): {{shellcode.hex()}}")
"""
else:
template += f"""
# {template_type} exploit template
print(f"Target: {{elf.path}}")
print(f"PIE: {{elf.pie}}")
print(f"NX: {{elf.nx}}")
print(f"Canary: {{elf.canary}}")
"""
return ToolResult(ok=True, output=template)
# ── Tool registry ──────────────────────────────────────────────────
class ToolRegistry:
def __init__(self):
self._tools: dict[str, callable] = {
"http": _run_http,
"sqlmap": _run_sqlmap,
"nuclei": _run_nuclei,
"ffuf": _run_ffuf,
"dalfox": _run_dalfox,
"zap": _run_zap,
"xss_confirm": _run_xss_confirm,
"z3": _run_z3,
"sage": _run_sage,
"rsa": _run_rsa_tool,
"factordb": _run_factordb,
"hashcat": _run_hashcat,
"hashid": _run_hashid,
"decode": _run_decode,
"js_deobfuscate": _run_js_deobf,
"sourcemap": _run_sourcemap,
"wasm_decompile": _run_wasm_decompile,
"triage": _run_triage,
"ropgadget": _run_ropgadget,
"pwntools": _run_pwntools,
"exploit_template": _run_exploit_template,
}
def list_tools(self) -> list[str]:
return list(self._tools.keys())
def has_tool(self, name: str) -> bool:
return name in self._tools
def run(self, name: str, args: dict, timeout_ms: int = 30_000) -> ToolResult:
if name not in self._tools:
return ToolResult(ok=False, error=f"unknown tool: {name}")
fn = self._tools[name]
return fn(args, timeout_ms)
def health_check(self) -> dict:
results = {}
for name in self._tools:
binary = _binary_for_tool(name)
if binary:
results[name] = {"available": _check_tool(binary)}
else:
results[name] = {"available": True} # Python-based, assume available
return {
"tools": results,
"available_count": sum(1 for v in results.values() if v["available"]),
"total_count": len(results),
}
def _binary_for_tool(name: str) -> Optional[str]:
mapping = {
"http": "curl",
"sqlmap": "sqlmap",
"nuclei": "nuclei",
"ffuf": "ffuf",
"dalfox": "dalfox",
"zap": "zap-cli",
"xss_confirm": "curl",
"z3": "z3",
"sage": "sage",
"hashcat": "hashcat",
"hashid": "hashid",
"pwntools": "python3",
"ropgadget": "ROPgadget",
}
return mapping.get(name)