mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-10-04 06:56:56 +02:00
fix(web): REPL spawn passed run-only flags as globals, breaking every run
The REPL-backed path (run/whitebox/greybox) spawns `neurosploit` with NO
subcommand, so only global flags are valid in argv — but authArgs() emitted
run-subcommand flags there (--environment, --policy, --in-scope, --budget,
--compliance, --revalidate-poc, --token-limit, --deep-test-limit, --order,
--sample-per-route, --scope-file). clap aborted on the first one
("unexpected argument '--environment'"), so the engagement died at launch and
the live view sat empty. authArgs now emits only the real global flags with
their global names (--session-environment / --session-in-scope / --session-policy,
plus --capability-token/--transport/--oob-*/--sms/--typesafe/--decision-backend/
--intercept/--sandbox); run-only knobs ride the REPL script or defaults.
Also:
- sidebar: a disk run whose status says "running" but has no live job is shown
as "interrupted", not "running" (no more stale RUNNING entries); brand-new
in-memory jobs are injected so an engagement appears the moment it starts;
new "Interrupted" group; clicking a running/interrupted row attaches the live
stream or offers resume.
- stop: robust now — graceful /stop then SIGTERM/SIGKILL fallback, a second
press escalates, a job whose child already exited is marked done so the UI
stops showing it as running.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
999d9c2209
commit
e1350c0c4a
2 files changed
+114
-20
No files matched your search
+49
-1
@@ -1625,7 +1625,8 @@ function renderSidebar() {
|
||||
const match = (r) => !q || `${r.name} ${r.target} ${r.id}`.toLowerCase().includes(q);
|
||||
const runs = state.runs.filter(match);
|
||||
const running = runs.filter((r) => r.state === 'running');
|
||||
const past = runs.filter((r) => r.state !== 'running');
|
||||
const interrupted = runs.filter((r) => r.state === 'interrupted');
|
||||
const past = runs.filter((r) => r.state !== 'running' && r.state !== 'interrupted');
|
||||
|
||||
if (running.length) {
|
||||
const wrap = document.createElement('div');
|
||||
@@ -1646,6 +1647,16 @@ function renderSidebar() {
|
||||
root.appendChild(wrap);
|
||||
}
|
||||
|
||||
if (interrupted.length) {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'sb-group';
|
||||
wrap.innerHTML = `<div class="sb-group-head"><span class="caret">▾</span><span>Interrupted</span><span class="count">${interrupted.length}</span></div><div class="sb-items"></div>`;
|
||||
wrap.querySelector('.sb-group-head').addEventListener('click', () => wrap.classList.toggle('collapsed'));
|
||||
const items = wrap.querySelector('.sb-items');
|
||||
for (const r of interrupted) items.appendChild(runButton(r));
|
||||
root.appendChild(wrap);
|
||||
}
|
||||
|
||||
const folders = runFolders(past);
|
||||
if (!folders.length) {
|
||||
const empty = document.createElement('div');
|
||||
@@ -1682,6 +1693,29 @@ function renderSidebar() {
|
||||
}
|
||||
}
|
||||
|
||||
// Resume an interrupted run from the sidebar: if a persisted job exists for it,
|
||||
// relaunch it live; otherwise just open its detail (partial findings on disk).
|
||||
async function resumeFromSidebar(run) {
|
||||
let jobs = [];
|
||||
try { jobs = await api('/api/exploit'); } catch { /* fall through to detail */ }
|
||||
const job = jobs.find((j) => (j.interrupted && j.resumable) && (j.runId === run.id || j.id === run.id || j.id === run.jobId));
|
||||
if (job) {
|
||||
try {
|
||||
await api(`/api/exploit/${job.id}/resume`, { method: 'POST' });
|
||||
localStorage.setItem(ACTIVE_JOB_KEY, job.id);
|
||||
attachLiveJob(job.id, run.target, run.name, job.pinnedAgents || []);
|
||||
await refreshRuns();
|
||||
return;
|
||||
} catch (e) {
|
||||
toast(`Couldn't resume: ${e.message}`, 'error', 8000);
|
||||
}
|
||||
}
|
||||
// No resumable job — show what's on disk.
|
||||
show($('#wizardView'), false); show($('#liveView'), false); show($('#dashView'), false); show($('#detailView'), true);
|
||||
loadDetail(run.id);
|
||||
renderSidebar();
|
||||
}
|
||||
|
||||
function openRun(run) {
|
||||
state.currentDetailId = run.id;
|
||||
if (run.state === 'running' && state.currentJob && run.id === state.currentJob.runId) {
|
||||
@@ -1689,6 +1723,20 @@ function openRun(run) {
|
||||
renderSidebar();
|
||||
return;
|
||||
}
|
||||
// A running job we're not already attached to (e.g. opened in another tab, or
|
||||
// just started): reconnect its live stream instead of showing a static detail.
|
||||
if (run.state === 'running' && (run.jobId || run.id)) {
|
||||
const jid = run.jobId || run.id;
|
||||
localStorage.setItem(ACTIVE_JOB_KEY, jid);
|
||||
attachLiveJob(jid, run.target, run.name, []);
|
||||
renderSidebar();
|
||||
return;
|
||||
}
|
||||
if (run.state === 'interrupted') {
|
||||
// Offer to resume where it left off; falls back to the static detail view.
|
||||
resumeFromSidebar(run);
|
||||
return;
|
||||
}
|
||||
show($('#wizardView'), false); show($('#liveView'), false); show($('#dashView'), false); show($('#detailView'), true);
|
||||
loadDetail(run.id);
|
||||
renderSidebar();
|
||||
|
||||
Reference in new issue
Block a user