mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-06-30 07:15:30 +02:00
Add minimalist web GUI for the v3.3.0 engine
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>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user