mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-08-26 11:22:37 +02:00
feat(web): drive run/whitebox/greybox exploitation through a real REPL session
Root cause of "can't send prompts while a run streams": /api/exploit spawned a plain `neurosploit run ...` subprocess, and that CLI path (run_mode() in main.rs) never reads stdin - it only waits on the task or Ctrl-C. The ONLY thing in the harness that keeps accepting input while an engagement streams is the interactive REPL's background-run loop. So: - New startJobViaRepl(): for mode run/whitebox/greybox, spawns a bare `neurosploit` REPL session and scripts it via stdin (/target or /repo, /model, /sub, /mcp, /votes, /chain, /recon, /focus, /objective, /scope-out, /creds, /only <agents> or /only clear, then /run) instead of building CLI args. Same underlying pipeline, same tagged output lines, so all existing parsing (findings/phase/progress/runId) works unchanged. host/aitest/skills modes stay on the old one-shot startJob() - they need onboarding's scope picker, an interactive arrow-key menu that silently skips itself over a piped stdin, so they can't be scripted this way. - New POST /api/exploit/:id/input writes a line to the session's stdin - natural language, /status, /continue, anything the REPL accepts - and the live run view grows a "send prompt" box (in the Activity log tab) for it, shown only when the job reports interactive: true. - Stop, for an interactive job, now sends the REPL's own graceful '/stop\n1\n' (validate what's found, then report) instead of SIGINT - the REPL's own input loop has no signal handler, so SIGINT there would just kill the process outright and skip the report step. Non- interactive jobs still get SIGINT (run_mode() does catch that). - 'done' can no longer be process-exit only: an interactive session stays open after the engagement finishes (for /report, /continue, another /run), so ingestLine() now also flags done from the same "phase complete" content signal it already used for the phase field. Verified end-to-end: started an interactive job, confirmed `interactive: true` and a captured runId, sent /status and /agents mid- and post-run over the new /input endpoint (both accepted, session stayed alive and responsive after completion), and confirmed a non-interactive run is unaffected. Also: the missing "Activity log" tab a screenshot showed for a "running" engagement was the sidebar's detail-view fallback (2 tabs, no log) for a run whose Job object no longer exists in server memory - it happens when the Node process gets restarted while a spawned neurosploit child is still alive underneath it (an orphan from testing across many redeploys this session, not a code bug); the live view itself always had the tab. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0129WdYHccPsH27k5GGuwijd
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
07bed42467
commit
4fbe608a7a
@@ -443,6 +443,8 @@ function attachLiveJob(id, target, name, pinnedAgents) {
|
||||
$('#progressLabel').textContent = '0 / ? agents';
|
||||
updatePinnedLine();
|
||||
show($('#btnOpenReport'), false);
|
||||
show($('#sendPromptRow'), false);
|
||||
show($('#sendPromptHelp'), false);
|
||||
|
||||
const es = new EventSource(`/api/exploit/${id}/events`);
|
||||
state.currentJob.es = es;
|
||||
@@ -502,6 +504,29 @@ function appendLog(line) {
|
||||
list.scrollTop = list.scrollHeight;
|
||||
}
|
||||
|
||||
// Only run/whitebox/greybox jobs are REPL-backed (interactive: true) — the
|
||||
// session keeps reading stdin while the engagement streams, so this is a
|
||||
// real command line into the SAME process, not a fire-and-forget note.
|
||||
$('#sendPromptInput').addEventListener('keydown', async (e) => {
|
||||
if (e.key !== 'Enter' || !state.currentJob) return;
|
||||
const line = e.target.value;
|
||||
if (!line.trim()) return;
|
||||
e.target.value = '';
|
||||
const div = document.createElement('div');
|
||||
div.className = 'log-line log-echo';
|
||||
div.textContent = `❭ ${line}`;
|
||||
const list = $('#logList');
|
||||
list.appendChild(div);
|
||||
list.scrollTop = list.scrollHeight;
|
||||
try {
|
||||
await api(`/api/exploit/${state.currentJob.id}/input`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ line }),
|
||||
});
|
||||
} catch (err) {
|
||||
appendLog(`[web] couldn't send: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
function findingRow(f, idx) {
|
||||
return `<tr data-idx="${idx}">
|
||||
<td><span class="sev ${sevClass(f.severity)}">${esc(f.severity)}</span></td>
|
||||
@@ -538,6 +563,9 @@ function addFinding(f) {
|
||||
function applySnapshot(snap) {
|
||||
$('#livePhase').textContent = snap.phase;
|
||||
state.currentJob.runId = snap.runId;
|
||||
state.currentJob.interactive = !!snap.interactive;
|
||||
show($('#sendPromptRow'), snap.interactive && !snap.done);
|
||||
show($('#sendPromptHelp'), snap.interactive && !snap.done);
|
||||
if (snap.pinnedAgents?.length && !state.currentJob.pinnedAgents.length) {
|
||||
state.currentJob.pinnedAgents = snap.pinnedAgents;
|
||||
updatePinnedLine();
|
||||
|
||||
Reference in New Issue
Block a user