New web/ app (zero npm deps, Node http built-ins only):
- server.js reads agents_md/ to build a categorized lead board (435 agents
auto-classified into Business Logic / Broken Access Control / Injection /
LLM Application / Auth & Session / SSRF / API / Cloud & Infra / etc.),
reads runs/ for history, and spawns the compiled neurosploit CLI binary
for every exploitation job — structured findings/phase/progress are parsed
from its stdout (finding_json:/phase lines), same signal the TUI uses.
- REPL drawer spawns `neurosploit` with no subcommand (real interactive
session, Reader::Plain over the piped stdin) and streams stdin/stdout —
every /command works exactly as in a terminal, nothing reimplemented.
- SSE endpoints for both job and REPL streams; run/finding/report assets
served under /api/runs/:id/asset/*.
- public/{index,app.js,style.css}: lead board with category toggles + custom
leads + Start Exploitation, live run view (progress/findings/log), run
detail view, REPL drawer — screenshot-inspired layout.
- web/API.md: full endpoint reference. web/README.md: quick start.
Bump version 3.6.9 -> 4.0.0 (Cargo.toml, CLI banners, README/TUTORIAL).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129WdYHccPsH27k5GGuwijd
9.0 KiB
NeuroSploit Web Console — API reference
Backend: web/server.js (Node, zero external dependencies). It does three things:
- Serves the SPA in
web/public/. - Reads
agents_md/andruns/from the repo root to build the lead board and run history. - Shells out to the compiled
neurosploitCLI binary (neurosploit-rs/target/release/neurosploit) for every exploitation run and for the REPL — the web UI never reimplements harness logic, it only drives the real CLI and parses its stdout.
Base URL: http://localhost:4173 (override with PORT or NEUROSPLOIT_WEB_PORT).
All responses are JSON unless noted. All endpoints are same-origin; there is no auth layer — run this only on a trusted machine/network, same trust model as the CLI itself.
Meta
GET /api/meta
Server/version info.
{ "version": "4.0.0", "binary": "/opt/neurosploit-rs/neurosploit-rs/target/release/neurosploit", "root": "/opt/neurosploit-rs" }
Agents / lead board
GET /api/agents
Reads every agents_md/{vulns,ai,infra,code,chains,recon,meta}/*.md, extracts name (filename
stem), title (first # heading), cwe (first CWE-\d+ match), kind (source directory), and
classifies each into a UI category (category) via a keyword taxonomy (Business Logic, Broken
Access Control, Injection, Cross-Site Scripting, LLM Application, Auth & Session, SSRF & Network,
API & GraphQL, Cloud & Infra, Client-Side, Cryptography, Rate Limiting & DoS, Cache & CDN,
Recon & Fingerprint, Linux Host, Windows Host, Attack Chains, Code Review, Recon, Other).
meta/ (orchestration/doctrine agents) is loaded but excluded from categories — those aren't
selectable "leads". Cached in-memory for 5s.
{
"total": 435,
"agents": [ { "id": "sqli_error", "name": "sqli_error", "title": "SQL Injection (Error-Based) Specialist Agent", "cwe": "CWE-89", "kind": "vuln", "category": "Injection" } ],
"categories": [ { "category": "Business Logic", "agents": [ /* Agent[] */ ] } ]
}
An agent's id/name is exactly what the CLI's --only <name> flag expects (see neurosploit agents).
Runs (history)
GET /api/runs
Lists runs/ns-* directories, newest first, with a summary read from each run's
meta.json / status.json / findings.json.
[ { "id": "ns-1787504238-testphp_vulnweb_com", "ts": 1787504238, "target": "http://testphp.vulnweb.com/", "state": "running", "findings": 3, "severities": { "High": 1, "Medium": 2 }, "hasReport": false } ]
state mirrors the CLI's status.json: running | complete | stopped-raw | discarded | unknown.
GET /api/runs/:id
Full detail for one run: { id, meta, status, findings, assets }. findings is the raw
findings.json array (see Finding shape below). assets lists which generated
files exist (report.html, report.pdf, report.md, recon.md, exploitation.md).
GET /api/runs/:id/asset/:path
Serves a file from that run's workdir (e.g. report.html, report.pdf, evidence/foo.png).
Path-traversal-guarded (resolved path must stay under the run dir). Use this to embed/open the
generated report from the browser.
Exploitation jobs (live runs)
Starting a job spawns neurosploit <mode> <target> [flags...] --verbose as a child process and
parses its stdout/stderr line-by-line into structured events — the same signal the interactive
REPL's status line uses (phase, agent counts, findings, report path).
POST /api/exploit
Body:
{
"mode": "run", // run | whitebox | greybox | host | aitest | skills
"target": "https://example.com", // required for run/host/aitest/greybox
"repo": "owner/repo", // required for whitebox; source repo for greybox
"models": ["anthropic:claude-opus-4-8"], // optional, repeatable in the CLI
"votes": 3, // --vote-n
"chainDepth": 2, // --chain-depth
"recon": 3, // --recon (1-4)
"maxAgents": 0, // --max-agents (0 = all)
"subscription": false, // --subscription
"offline": false, // --offline
"mcp": false, // --mcp
"creds": "creds.yaml", // --creds
"focus": "injection and business logic", // --focus
"objective": "pre-launch review of checkout", // --objective
"outOfScope": "staging.example.com", // --out-of-scope
"agents": ["sqli_error", "idor"] // --only <name>, repeated — the lead-board selection
}
Response: { "id": "<job-uuid>" }. This id is the web job id, not the run id — the CLI's own
ns-<timestamp>-<target> run id is discovered from its own log line and exposed as runId in the
job snapshot once the engagement starts writing to runs/.
If agents is empty, no --only flag is passed and the harness falls back to its normal
recon-driven agent selection (the intelligent default) — the lead board's "0 selected" state is a
valid, meaningful choice, not an error.
GET /api/exploit
List all jobs known to this server process (in-memory; lost on restart) as snapshots (see below).
GET /api/exploit/:id
One job's current snapshot:
{
"id": "d98f51ad-...", "target": "http://testphp.vulnweb.com/",
"runId": "ns-1787504238-testphp_vulnweb_com",
"phase": "exploiting", "findings": [ /* Finding[] */ ],
"agents": 245, "agentsDone": 12, "done": false, "exitCode": null,
"reportUrl": null, "startedAt": 1787504238594
}
phase tracks the same lifecycle the REPL's /status shows: starting → recon → planning → exploiting → validating → chaining → complete, or paused (quota) / paused (auth) if the
harness parks the run (token/quota exhaustion or auth failure — findings are preserved either way).
POST /api/exploit/:id/stop
Sends SIGINT to the child process — identical to pressing Ctrl-C in the terminal. The harness's
own graceful-stop logic decides whether to keep partial findings.
GET /api/exploit/:id/events (Server-Sent Events)
Live stream. On connect, replays every buffered event so a reconnecting client doesn't miss history, then streams new ones. Named SSE events:
| event | data | meaning |
|---|---|---|
log |
{ "type": "log", "line": "..." } |
one stdout/stderr line (ANSI stripped) |
finding |
{ "type": "finding", "finding": {…} } |
a finding_json: line, parsed |
snapshot |
job snapshot (see above) | phase/progress update |
done |
job snapshot with done: true |
process exited; stream closes |
Client example:
const es = new EventSource(`/api/exploit/${id}/events`);
es.addEventListener('finding', (e) => console.log(JSON.parse(e.data).finding));
es.addEventListener('done', () => es.close());
REPL sessions
Spawns the CLI with no subcommand — the same interactive session neurosploit launches from
a terminal — and pipes stdin/stdout. Because stdin isn't a TTY, the CLI's Reader::Plain path
takes over: it prints each prompt to stdout then reads one line at a time from stdin, so it works
perfectly over a plain pipe. This is a real harness process; every /command (/run, /status,
/stop, /model, /target, natural-language input, etc.) behaves exactly as it would in a
terminal.
POST /api/repl
Starts a session. Response: { "id": "<session-uuid>" }.
POST /api/repl/:id/input
Body: { "line": "/status" }. Writes line + "\n" to the child's stdin.
POST /api/repl/:id/stop
Sends SIGTERM to the session's child process.
GET /api/repl/:id/events (SSE)
| event | data | meaning |
|---|---|---|
data |
{ "chunk": "..." } |
raw stdout/stderr chunk (ANSI stripped), not line-buffered |
close |
{} |
child process exited |
Replays the session's buffered output (capped at the last 5000 chunks) on connect, same as the exploit stream.
Finding shape
Findings are exactly the harness's harness::types::Finding struct (see
neurosploit-rs/crates/harness/src/types.rs), serialized as JSON — the web UI does not transform
or rename any field:
{
id: string, agent: string, title: string, severity: string, cwe: string, cvss: string,
endpoint: string, payload: string, evidence: string, impact: string, remediation: string,
confidence: number, validated: boolean, votes: string,
owasp: string, mitre: string, stage: string, exploitability: string, business_impact: string,
chains_from: string[], auth_context: string, account: string, secret: string,
review_status: string, review_reason: string, screenshots: string[],
}
Running it
cd neurosploit-rs && cargo build --release # once, or after a harness change
node web/server.js # http://localhost:4173
PORT (or NEUROSPLOIT_WEB_PORT) overrides the port. The server auto-locates the compiled binary
under neurosploit-rs/target/{release,debug}/neurosploit relative to the repo root; if neither
exists, /api/exploit and /api/repl return a 500 with a build hint.