mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-06-30 07:15:30 +02:00
22a7302a35
Zero-dependency (stdlib http.server) front-end exposing only the essential options — URL, backend, model, collaborator, RL + Playwright-MCP toggles — with a live progress console. Calls neurosploit_agent directly; no npm/build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
140 lines
5.2 KiB
Python
140 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
NeuroSploit v3.3.0 — minimalist web GUI server.
|
|
|
|
A tiny, dependency-free (Python stdlib only) web front-end for the autonomous
|
|
engine. It exposes just the essential options — target URL, backend, model,
|
|
collaborator, and the RL / Playwright-MCP toggles — and launches an engagement.
|
|
|
|
python3 webgui/server.py # serves http://127.0.0.1:8787
|
|
|
|
No npm, no build step, no FastAPI. It talks to neurosploit_agent directly.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import threading
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, ROOT)
|
|
|
|
from neurosploit_agent import backends, models # noqa: E402
|
|
from neurosploit_agent.agent_loader import AgentLibrary # noqa: E402
|
|
from neurosploit_agent.config import RunConfig # noqa: E402
|
|
from neurosploit_agent.orchestrator import run_engagement # noqa: E402
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
_RUNS = {} # run_id -> {log:[], done:bool, result:dict}
|
|
_LOCK = threading.Lock()
|
|
_PROV_FOR_BACKEND = {"claude": "anthropic", "codex": "openai", "grok": "xai"}
|
|
|
|
|
|
def _info():
|
|
lib = AgentLibrary()
|
|
det = backends.detect()
|
|
provs = {}
|
|
for p in models.PROVIDERS.values():
|
|
provs[p.key] = {"label": p.label,
|
|
"models": [{"id": m.id, "label": m.label} for m in p.models]}
|
|
return {
|
|
"version": "3.3.0",
|
|
"agents": lib.counts(),
|
|
"backends": [{"key": b.key, "label": b.label, "version": b.version()} for b in det],
|
|
"providers": provs,
|
|
"backend_provider": _PROV_FOR_BACKEND,
|
|
}
|
|
|
|
|
|
def _start_run(params):
|
|
run_id = "run-%d" % (len(_RUNS) + 1)
|
|
with _LOCK:
|
|
_RUNS[run_id] = {"log": [], "done": False, "result": None}
|
|
|
|
def progress(msg):
|
|
with _LOCK:
|
|
_RUNS[run_id]["log"].append(msg)
|
|
|
|
def worker():
|
|
try:
|
|
backend = params.get("backend") or (backends.detect()[0].key if backends.detect() else "claude")
|
|
provider = params.get("provider") or _PROV_FOR_BACKEND.get(backend, "anthropic")
|
|
mlist = models.list_models(provider)
|
|
model = params.get("model") or (mlist[0].id if mlist else "")
|
|
url = params["url"]
|
|
if not url.startswith(("http://", "https://")):
|
|
url = "https://" + url
|
|
cfg = RunConfig(
|
|
target=url, scope=params.get("scope") or url, backend=backend,
|
|
provider=provider, model=model, collaborator=params.get("collaborator", ""),
|
|
use_rl=bool(params.get("rl", True)), use_mcp=bool(params.get("mcp", True)),
|
|
dry_run=bool(params.get("dry_run", False)),
|
|
)
|
|
res = run_engagement(cfg, progress=progress)
|
|
with _LOCK:
|
|
_RUNS[run_id]["result"] = {
|
|
"returncode": res["returncode"], "workdir": res["workdir"],
|
|
"findings": res["findings"], "agents_ran": res["agents_ran"],
|
|
}
|
|
except Exception as e: # surface errors to the UI
|
|
progress(f"ERROR: {e}")
|
|
with _LOCK:
|
|
_RUNS[run_id]["result"] = {"error": str(e)}
|
|
finally:
|
|
with _LOCK:
|
|
_RUNS[run_id]["done"] = True
|
|
|
|
threading.Thread(target=worker, daemon=True).start()
|
|
return run_id
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def _send(self, code, body, ctype="application/json"):
|
|
data = body if isinstance(body, bytes) else body.encode("utf-8")
|
|
self.send_response(code)
|
|
self.send_header("Content-Type", ctype)
|
|
self.send_header("Content-Length", str(len(data)))
|
|
self.end_headers()
|
|
self.wfile.write(data)
|
|
|
|
def log_message(self, *a):
|
|
pass
|
|
|
|
def do_GET(self):
|
|
if self.path in ("/", "/index.html"):
|
|
self._send(200, open(os.path.join(HERE, "index.html"), "rb").read(), "text/html; charset=utf-8")
|
|
elif self.path == "/api/info":
|
|
self._send(200, json.dumps(_info()))
|
|
elif self.path.startswith("/api/status/"):
|
|
rid = self.path.rsplit("/", 1)[-1]
|
|
with _LOCK:
|
|
st = _RUNS.get(rid)
|
|
self._send(200 if st else 404, json.dumps(st or {"error": "unknown run"}))
|
|
else:
|
|
self._send(404, json.dumps({"error": "not found"}))
|
|
|
|
def do_POST(self):
|
|
if self.path != "/api/run":
|
|
return self._send(404, json.dumps({"error": "not found"}))
|
|
n = int(self.headers.get("Content-Length", 0))
|
|
try:
|
|
params = json.loads(self.rfile.read(n) or b"{}")
|
|
except Exception:
|
|
return self._send(400, json.dumps({"error": "bad json"}))
|
|
if not params.get("url"):
|
|
return self._send(400, json.dumps({"error": "url required"}))
|
|
rid = _start_run(params)
|
|
self._send(200, json.dumps({"run_id": rid}))
|
|
|
|
|
|
def main():
|
|
host = os.getenv("NEUROSPLOIT_GUI_HOST", "127.0.0.1")
|
|
port = int(os.getenv("NEUROSPLOIT_GUI_PORT", "8787"))
|
|
print(f"NeuroSploit v3.3.0 GUI → http://{host}:{port}")
|
|
ThreadingHTTPServer((host, port), Handler).serve_forever()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|