Files
NeuroSploit/web/public/index.html
T
CyberSecurityUPandClaude Sonnet 5 4fbe608a7a 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
2026-08-23 16:04:55 -03:00

355 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>NeuroSploit v4.0.0 — Console</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧠</text></svg>">
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<div class="app">
<!-- ============ SIDEBAR ============ -->
<aside class="sidebar" id="sidebar">
<div class="sb-top">
<div class="brand"><span class="mark">NS</span> NeuroSploit</div>
<button class="icon-btn" id="btnThemeToggle" title="Toggle light / dark theme"></button>
</div>
<button class="sb-new" id="btnNewEngagement">+ New engagement</button>
<div class="sb-groups" id="sbGroups"><!-- populated by app.js --></div>
<div class="sb-bottom">
<span class="sb-version" id="sbVersion">v4.0.0</span>
<div class="sb-bottom-actions">
<button class="icon-btn" id="btnOpenAuth" title="Auth &amp; API keys">🔑</button>
<button class="icon-btn" id="btnOpenRepl" title="Open REPL">❭_</button>
</div>
</div>
</aside>
<!-- ============ MAIN ============ -->
<main class="main">
<!-- ============ WIZARD (new engagement) ============ -->
<section class="wizard" id="wizardView">
<header class="topbar">
<div>
<div class="topbar-title">New engagement</div>
<div class="topbar-sub">Asset → Scope &amp; Auth → Leads → Model &amp; Run → Review</div>
</div>
<div class="topbar-spacer"></div>
<button class="btn" id="btnOpenAuth2">🔑 Auth &amp; Keys</button>
</header>
<nav class="stepper" id="stepper">
<button class="step-tab active" data-step="0"><span class="n">1</span> Asset</button>
<button class="step-tab" data-step="1"><span class="n">2</span> Scope &amp; Auth</button>
<button class="step-tab" data-step="2"><span class="n">3</span> Leads</button>
<button class="step-tab" data-step="3"><span class="n">4</span> Model &amp; Run</button>
<button class="step-tab" data-step="4"><span class="n">5</span> Review</button>
</nav>
<div class="wizard-body">
<!-- Step 1 — Asset -->
<div class="wizard-panel" data-panel="0">
<div class="field-group">
<label class="field-label">Engagement name</label>
<input id="fieldName" type="text" placeholder="e.g. Keystone Digital Banking" />
<div class="field-help">Identifies this engagement in the sidebar and run history — required.</div>
</div>
<div>
<div class="section-title">What are you testing?</div>
<div class="section-desc">Pick the engagement type — this decides which CLI subcommand runs underneath.</div>
</div>
<div class="mode-tiles" id="modeTiles">
<button class="mode-tile" data-mode="run"><span class="t">Black-box</span><span class="d">URL only — recon-driven</span></button>
<button class="mode-tile" data-mode="whitebox"><span class="t">White-box</span><span class="d">Source repo — SAST agents</span></button>
<button class="mode-tile" data-mode="greybox"><span class="t">Grey-box</span><span class="d">URL + source together</span></button>
<button class="mode-tile" data-mode="host"><span class="t">Host / Infra</span><span class="d">IP — Linux/Windows/AD</span></button>
<button class="mode-tile" data-mode="aitest"><span class="t">AI / LLM</span><span class="d">Live AI agent or MCP endpoint</span></button>
</div>
<div class="field-group" id="fieldTargetGroup">
<label class="field-label" id="targetLabel">Target URL</label>
<input id="fieldTarget" type="text" placeholder="https://target.example.com" />
<div class="field-help" id="targetHelp">The application, host, or endpoint to test.</div>
</div>
<div class="field-group" id="fieldRepoGroup" hidden>
<label class="field-label">Source repo</label>
<input id="fieldRepo" type="text" placeholder="owner/repo, a GitHub URL, or a local path" />
<div class="field-help">Cloned automatically if it's a GitHub URL or owner/repo shorthand.</div>
</div>
</div>
<!-- Step 2 — Scope & Auth -->
<div class="wizard-panel" data-panel="1" hidden>
<div>
<div class="section-title">Objective &amp; focus</div>
<div class="section-desc">Steers what the agents prioritise and what counts as impact.</div>
</div>
<div class="field-group">
<label class="field-label">Objective</label>
<textarea id="fieldObjective" placeholder="e.g. Pre-launch review of the checkout flow — prove any path to unauthorized order access."></textarea>
</div>
<div class="field-group">
<label class="field-label">Focus</label>
<textarea id="fieldFocus" placeholder="e.g. Prioritize the paths most likely to cause data leakage."></textarea>
</div>
<div class="field-group">
<label class="field-label">Out of scope</label>
<textarea id="fieldOutOfScope" placeholder="Hosts, paths, or techniques the agents must not touch."></textarea>
</div>
<div>
<div class="section-title">Authentication</div>
<div class="section-desc">Test as a logged-in user. Configured in the <button class="btn btn-sm" id="btnOpenAuth3" style="display:inline">🔑 Auth &amp; Keys</button> menu.</div>
</div>
</div>
<!-- Step 3 — Leads -->
<div class="wizard-panel" data-panel="2" hidden style="max-width: none;">
<div>
<div class="section-title">Set the action plan</div>
<div class="section-desc">Toggle specific leads to test, or leave everything off to let recon-driven auto-selection choose.</div>
</div>
<div class="lead-toolbar">
<div class="search-wrap">
<span class="search-icon"></span>
<input id="leadSearch" type="text" placeholder="Search lead" />
</div>
<div class="chips">
<button class="chip chip-active" data-filter="all">All <span id="chipAll">0</span></button>
<button class="chip" data-filter="selected">Selected <span id="chipSelected">0</span></button>
<button class="chip" data-filter="excluded">Excluded <span id="chipExcluded">0</span></button>
</div>
<div class="topbar-spacer"></div>
<button class="btn btn-sm" id="btnSelectAll">Select all</button>
<button class="btn btn-sm" id="btnClearAll">Clear all</button>
<button class="btn btn-sm" id="btnCustomLead">+ Custom lead</button>
</div>
<div class="field-help" style="margin: -8px 0 4px;">Tip: click a category's switch to select/deselect every lead in it at once.</div>
<div class="custom-leads" id="customLeadsList"></div>
<div class="categories" id="categories"><!-- populated --></div>
</div>
<!-- Step 4 — Model & Run -->
<div class="wizard-panel" data-panel="3" hidden>
<div>
<div class="section-title">Model</div>
<div class="section-desc">Pick a provider and model from the harness's live catalog.</div>
</div>
<div class="field-row">
<div class="field-group">
<label class="field-label">Provider</label>
<select id="fieldProvider"></select>
</div>
<div class="field-group">
<label class="field-label">Model</label>
<select id="fieldModelSelect"></select>
</div>
</div>
<div class="field-group">
<label class="field-label">Auth mode</label>
<div class="auth-mode-toggle" id="authModeToggle">
<button data-mode="api" class="selected">API key</button>
<button data-mode="subscription">Subscription (local CLI login)</button>
</div>
<div class="field-help" id="authModeHelp">Uses the API key set in Auth &amp; Keys for this provider.</div>
</div>
<div class="check-row"><input type="checkbox" id="fieldMcp" /> <label for="fieldMcp">Playwright MCP (browser tool access, subscription backends only)</label></div>
<div>
<div class="section-title">Run settings</div>
</div>
<div class="field-row">
<div class="field-group"><label class="field-label">Votes</label><input class="narrow" id="fieldVotes" type="number" min="1" max="9" value="3" /></div>
<div class="field-group"><label class="field-label">Chain depth</label><input class="narrow" id="fieldChain" type="number" min="0" max="5" value="2" /></div>
<div class="field-group"><label class="field-label">Recon intensity</label>
<select class="narrow" id="fieldRecon">
<option value="1">1 · quick</option>
<option value="2">2 · standard</option>
<option value="3" selected>3 · deep</option>
<option value="4">4 · exhaustive</option>
</select>
</div>
</div>
</div>
<!-- Step 5 — Review -->
<div class="wizard-panel" data-panel="4" hidden>
<div>
<div class="section-title">Review</div>
<div class="section-desc">Confirm before launching — this spawns the real CLI harness.</div>
</div>
<div class="review-grid" id="reviewGrid"></div>
</div>
</div>
<footer class="wizard-footer">
<div class="summary-line" id="wizardSummary"></div>
<div style="display:flex; gap:8px;">
<button class="btn" id="btnStepBack">← Back</button>
<button class="btn btn-primary" id="btnStepNext">Next →</button>
<button class="btn btn-primary" id="btnLaunch" hidden>Start Exploitation →</button>
</div>
</footer>
</section>
<!-- ============ LIVE RUN ============ -->
<section class="runpage" id="liveView" hidden>
<header class="run-head">
<div>
<div class="run-target" id="liveTarget"></div>
<div class="run-meta" id="liveTargetSub" style="font-family: var(--mono);"></div>
<div class="run-meta"><span class="phase-dot" id="phaseDot"></span><span id="livePhase">starting</span></div>
<div class="run-meta" id="livePinned" style="font-family: var(--mono);"></div>
</div>
<div class="run-actions">
<a class="btn" id="btnOpenReport" target="_blank" hidden>Open report</a>
<button class="btn btn-danger" id="btnStopRun">Stop</button>
<button class="btn" id="btnBackToBoard">← New engagement</button>
</div>
</header>
<div class="progress-wrap">
<div class="progress-bar" id="progressBar"><div class="progress-fill" id="progressFill"></div></div>
<div class="progress-label" id="progressLabel">0 / 0 agents</div>
</div>
<nav class="run-tabs">
<button class="run-tab active" data-tab="findings">Findings <span id="liveFindingsCount">0</span></button>
<button class="run-tab" data-tab="attackpath">Generative Attack Path Chaining</button>
<button class="run-tab" data-tab="log">Activity log</button>
</nav>
<div class="run-body">
<div class="run-tab-panel" data-tabpanel="findings"><table class="data-table" id="liveFindingsTable"><thead><tr><th>Severity</th><th>Title</th><th>Endpoint</th><th>CWE</th><th>Agent</th><th>Conf.</th></tr></thead><tbody></tbody></table><div class="empty-state" id="liveFindingsEmpty">No validated findings yet.</div></div>
<div class="run-tab-panel" data-tabpanel="attackpath" hidden><div id="liveAttackPath"></div></div>
<div class="run-tab-panel log-tab-panel" data-tabpanel="log" hidden>
<div class="log-panel" id="logList"></div>
<div class="send-prompt-row" id="sendPromptRow" hidden>
<span class="repl-prompt"></span>
<input id="sendPromptInput" type="text" autocomplete="off" spellcheck="false" placeholder="/status · /stop · /continue · or describe it in plain language" />
</div>
<div class="field-help" id="sendPromptHelp" hidden>This session stays interactive while the engagement runs — type a command or plain instruction and press Enter.</div>
</div>
</div>
</section>
<!-- ============ RUN DETAIL (past run) ============ -->
<section class="runpage" id="detailView" hidden>
<header class="run-head">
<div>
<div class="run-target" id="detailTarget"></div>
<div class="run-meta" id="detailTargetSub" style="font-family: var(--mono);"></div>
<div class="run-meta"><span class="phase-dot static" id="detailDot"></span><span id="detailState"></span></div>
</div>
<div class="run-actions">
<a class="btn" id="detailOpenReport" target="_blank" hidden>Open report</a>
<button class="btn" id="btnDetailBack">← New engagement</button>
</div>
</header>
<nav class="run-tabs">
<button class="run-tab active" data-tab="findings">Findings <span id="detailFindingsCount">0</span></button>
<button class="run-tab" data-tab="attackpath">Generative Attack Path Chaining</button>
</nav>
<div class="run-body">
<div class="run-tab-panel" data-tabpanel="findings"><table class="data-table" id="detailFindingsTable"><thead><tr><th>Severity</th><th>Title</th><th>Endpoint</th><th>CWE</th><th>Agent</th><th>Conf.</th></tr></thead><tbody></tbody></table><div class="empty-state" id="detailFindingsEmpty">No validated findings.</div></div>
<div class="run-tab-panel" data-tabpanel="attackpath" hidden><div id="detailAttackPath"></div></div>
</div>
</section>
</main>
</div>
<!-- ============ AUTH & KEYS MODAL ============ -->
<div class="modal-overlay" id="authModal" hidden>
<div class="modal">
<div class="modal-head">
<div class="title">Auth &amp; Keys</div>
<button class="icon-btn" id="btnCloseAuth"></button>
</div>
<div class="modal-tabs">
<button class="modal-tab active" data-mtab="target">Target auth</button>
<button class="modal-tab" data-mtab="keys">API keys</button>
<button class="modal-tab" data-mtab="creds">Creds file</button>
</div>
<div class="modal-body">
<div class="modal-panel" data-mpanel="target">
<div class="field-group">
<label class="field-label">Auth header</label>
<input id="authHeader" type="text" placeholder="Authorization: Bearer &lt;token&gt; or Cookie: session=..." />
<div class="field-help">Used so agents test as a logged-in user. Kept only for this session, sent to the CLI as an ephemeral creds file.</div>
</div>
<div class="field-group">
<label class="field-label">Named roles (multi-identity access-control testing)</label>
<div class="role-list" id="roleList"></div>
<button class="btn btn-sm" id="btnAddRole" style="align-self:flex-start;">+ Add role</button>
<div class="field-help">Two or more roles enable IDOR/BOLA/BFLA cross-role testing.</div>
</div>
</div>
<div class="modal-panel" data-mpanel="keys" hidden>
<div class="field-help" style="margin-bottom:12px;">Keys are kept in this server process's memory only — never written to disk. Cleared on restart.</div>
<div id="providerKeyList"></div>
</div>
<div class="modal-panel" data-mpanel="creds" hidden>
<div class="field-group">
<label class="field-label">creds.yaml path (overrides target auth above)</label>
<input id="credsPath" type="text" placeholder="creds.yaml" />
<div class="field-help">An explicit file on disk — see neurosploit-rs/creds.example.yaml for the schema (jwt/header/cookie/login/roles/ssh/windows/cloud).</div>
</div>
</div>
</div>
</div>
</div>
<!-- ============ FINDING DETAIL MODAL ============ -->
<div class="modal-overlay" id="findingModal" hidden>
<div class="modal" style="width: 760px;">
<div class="modal-head">
<div>
<span class="sev" id="fmSev"></span>
<span class="title" id="fmTitle" style="margin-left:8px;"></span>
</div>
<button class="icon-btn" id="btnCloseFinding"></button>
</div>
<div class="modal-body">
<div class="review-grid" id="fmMeta" style="margin-bottom: var(--sp-5);"></div>
<div id="fmSection-evidence"></div>
<div id="fmSection-impact"></div>
<div id="fmSection-remediation"></div>
<div id="fmSection-chains"></div>
<div class="field-group">
<label class="field-label">Proof of concept</label>
<div id="fmPocList" class="field-help"></div>
</div>
</div>
</div>
</div>
<!-- ============ REPL DRAWER ============ -->
<div class="repl-drawer" id="replDrawer" hidden>
<div class="repl-head">
<span>NeuroSploit CLI harness — REPL</span>
<div>
<button class="icon-btn" id="btnReplRestart" title="Restart session"></button>
<button class="icon-btn" id="btnReplClose" title="Close"></button>
</div>
</div>
<div class="repl-output" id="replOutput"></div>
<div class="repl-input-row">
<span class="repl-prompt"></span>
<input id="replInput" type="text" autocomplete="off" spellcheck="false" placeholder="/help · /run · /status · or describe it in plain language" />
</div>
</div>
<button class="fab" id="fabRepl" title="Open REPL">❭_</button>
<script src="/app.js"></script>
</body>
</html>