Files
NeuroSploit/web/public/index.html
T
CyberSecurityUPandClaude Opus 5 0ef0ce8d94 feat(web): xterm.js terminal dock + front-end QA pass
Replaces the floating REPL drawer with a docked terminal, and fixes the
usability problems a screenshot audit of the console turned up.

Terminal (the reason for the change):

- The drawer rendered the harness into a <div>, so the server had to strip
  ANSI before sending it: colour, the box-drawn /status panel and the banner
  all arrived flattened, and long lines rewrapped mid-glyph. The stream is now
  sent verbatim and rendered by xterm.js (vendored, nothing fetched at
  runtime), decoded with a streaming UTF-8 decoder so a multi-byte character
  split across two reads survives.
- The drawer floated bottom-right, directly over "Next →" and "Start
  Exploitation" — the wizard's primary buttons. The dock is a flex child of
  .main, so opening it shortens the view instead of covering it. Drag its top
  edge to resize; the height is remembered.
- The child is spawned over a pipe, not a PTY, so it never echoes: line
  editing is local — echo, ←/→, Home/End, history, Tab completion over the
  slash commands, Ctrl+C/L/U/K/A/E. Ctrl-C is delivered as SIGINT by the
  server, since a raw 0x03 byte over a pipe interrupts nothing.
- A target picker switches the terminal between a standalone REPL session and
  the engagement currently running, so mid-run instructions go to the same
  process doing the testing.

QA fixes:

- Findings tables sorted by severity (a LOW above a CRITICAL made a 27-row
  result unreadable), with sortable headers, a severity summary that doubles
  as a filter, a text filter, a sticky header, and horizontal scroll confined
  to the table instead of the whole page.
- alert()/prompt() replaced by inline field errors, a custom-lead modal and
  toasts — a modal alert hid the very field it was complaining about.
- Lead categories start collapsed (412 leads over ~30 categories); search
  auto-expands what it matches and shows per-category hit counts.
- Sidebar rows truncate inside the rail (a long target URL used to spill past
  its border), and carry a worst-severity dot, finding count and age.
- Past-run header shows when it ran, how many agents ran, the recon asset,
  PoC count and run id — two runs of one target were indistinguishable.
- Off-canvas sidebar below 768px had no way to be opened; added the toggle.
- Long evidence values (cookies, tokens) now wrap instead of running under
  the finding modal's edge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvdGy9XtVWSdXDTa3FFLJv
2026-09-07 13:33:16 -03:00

431 lines
22 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="/vendor/xterm.css" />
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<button class="sidebar-toggle" id="btnSidebarToggle" title="Show / hide run history" aria-label="Show or hide run history">☰</button>
<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 terminal (Ctrl+`)">❭_</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="btnOpenTerm2">❭_ Terminal</button>
<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" for="fieldName">Engagement name</label>
<input id="fieldName" type="text" placeholder="e.g. Keystone – Digital Banking" aria-describedby="errName" />
<div class="field-error" id="errName" hidden></div>
<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" for="fieldTarget">Target URL</label>
<input id="fieldTarget" type="text" placeholder="https://target.example.com" aria-describedby="errTarget" />
<div class="field-error" id="errTarget" hidden></div>
<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" for="fieldRepo">Source repo</label>
<input id="fieldRepo" type="text" placeholder="owner/repo, a GitHub URL, or a local path" aria-describedby="errRepo" />
<div class="field-error" id="errRepo" hidden></div>
<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" for="fieldObjective">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" for="fieldFocus">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" for="fieldOutOfScope">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">🔑 Auth &amp; Keys</button> menu.</div>
</div>
</div>
<!-- Step 3 — Leads -->
<div class="wizard-panel wizard-panel-wide" data-panel="2" hidden>
<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" aria-hidden="true">⌕</span>
<input id="leadSearch" type="text" placeholder="Search leads by name or CWE" />
</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">Pinned <span id="chipSelected">0</span></button>
<button class="chip" data-filter="excluded">Not pinned <span id="chipExcluded">0</span></button>
</div>
<div class="topbar-spacer"></div>
<button class="btn btn-sm" id="btnExpandAll">Expand all</button>
<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 lead-tip">Categories start collapsed — click one to open it, or click its switch to pin every lead inside at once.</div>
<div class="custom-leads" id="customLeadsList" hidden></div>
<div class="categories" id="categories"><!-- populated --></div>
<div class="empty-state" id="leadsEmpty" hidden>No lead matches that search.</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" for="fieldProvider">Provider</label>
<select id="fieldProvider"></select>
</div>
<div class="field-group">
<label class="field-label" for="fieldModelSelect">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" for="fieldVotes">Votes</label><input class="narrow" id="fieldVotes" type="number" min="1" max="9" value="3" /><div class="field-help">Models that must agree before a finding is kept.</div></div>
<div class="field-group"><label class="field-label" for="fieldChain">Chain depth</label><input class="narrow" id="fieldChain" type="number" min="0" max="5" value="2" /><div class="field-help">How far findings get chained into attack paths.</div></div>
<div class="field-group"><label class="field-label" for="fieldRecon">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 class="field-help">Deeper recon finds more surface but costs more tokens.</div>
</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 class="footer-actions">
<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 class="run-head-main">
<div class="run-target" id="liveTarget">—</div>
<div class="run-meta" id="liveTargetSub"></div>
<div class="run-meta"><span class="phase-dot" id="phaseDot"></span><span id="livePhase">starting</span></div>
<div class="run-meta" id="livePinned"></div>
</div>
<div class="run-actions">
<button class="btn" id="btnOpenTerm3">❭_ Terminal</button>
<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">
<div class="findings-toolbar">
<div class="sev-summary" id="liveSevSummary"></div>
<div class="topbar-spacer"></div>
<div class="search-wrap">
<span class="search-icon" aria-hidden="true">⌕</span>
<input id="liveFindingSearch" type="text" placeholder="Filter findings" />
</div>
</div>
<div class="table-wrap">
<table class="data-table" id="liveFindingsTable">
<thead><tr><th data-sort="severity">Severity</th><th data-sort="title">Title</th><th data-sort="endpoint">Endpoint</th><th data-sort="cwe">CWE</th><th data-sort="agent">Agent</th><th data-sort="confidence" class="col-conf">Conf.</th></tr></thead>
<tbody></tbody>
</table>
</div>
<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" />
<button class="btn btn-sm" id="btnSendPromptTerm" title="Open this session in the full terminal">Open in terminal</button>
</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 class="run-head-main">
<div class="run-target" id="detailTarget">—</div>
<div class="run-meta" id="detailTargetSub"></div>
<div class="run-meta"><span class="phase-dot static" id="detailDot"></span><span id="detailState">—</span></div>
<div class="run-meta run-facts" id="detailFacts"></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">
<div class="findings-toolbar">
<div class="sev-summary" id="detailSevSummary"></div>
<div class="topbar-spacer"></div>
<div class="search-wrap">
<span class="search-icon" aria-hidden="true">⌕</span>
<input id="detailFindingSearch" type="text" placeholder="Filter findings" />
</div>
</div>
<div class="table-wrap">
<table class="data-table" id="detailFindingsTable">
<thead><tr><th data-sort="severity">Severity</th><th data-sort="title">Title</th><th data-sort="endpoint">Endpoint</th><th data-sort="cwe">CWE</th><th data-sort="agent">Agent</th><th data-sort="confidence" class="col-conf">Conf.</th></tr></thead>
<tbody></tbody>
</table>
</div>
<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>
<!-- ============ TERMINAL DOCK (real CLI harness session) ============ -->
<section class="term-dock" id="termDock" hidden>
<div class="term-resize" id="termResize" title="Drag to resize"></div>
<div class="term-head">
<span class="term-dot" id="termDot" data-state="off"></span>
<span class="term-title">NeuroSploit harness</span>
<select class="term-target" id="termTarget" title="Where this terminal's input goes">
<option value="session">standalone REPL session</option>
</select>
<span class="term-status" id="termStatus">disconnected</span>
<div class="topbar-spacer"></div>
<button class="term-btn" id="btnTermClear" title="Clear screen (Ctrl+L)">clear</button>
<button class="term-btn" id="btnTermRestart" title="Start a fresh harness session">restart</button>
<button class="term-btn" id="btnTermExpand" title="Expand / collapse the dock">expand</button>
<button class="icon-btn" id="btnTermClose" title="Close (Esc)">✕</button>
</div>
<div class="term-alert" id="termAlert" hidden></div>
<div class="term-host" id="termHost"></div>
<div class="term-hints" id="termHints">
<span class="k">Enter</span> send · <span class="k">↑↓</span> history · <span class="k">Tab</span> complete · <span class="k">Ctrl+C</span> interrupt · <span class="k">Ctrl+L</span> clear
</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" for="authHeader">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" for="credsPath">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>
<!-- ============ CUSTOM LEAD MODAL ============ -->
<div class="modal-overlay" id="leadModal" hidden>
<div class="modal modal-sm">
<div class="modal-head">
<div class="title">Custom lead</div>
<button class="icon-btn" id="btnCloseLead">✕</button>
</div>
<div class="modal-body">
<div class="field-group">
<label class="field-label" for="leadDesc">What should this lead test?</label>
<textarea id="leadDesc" placeholder="e.g. Abuse the invoice PDF export to read internal files via the template engine."></textarea>
<div class="field-help">Claude (Opus, subscription) generates a real specialist agent file for it and pins it. If generation is unavailable, the text is folded into <code>--focus</code> instead.</div>
</div>
<div class="field-error" id="errLead" hidden></div>
</div>
<div class="modal-foot">
<button class="btn" id="btnLeadCancel">Cancel</button>
<button class="btn btn-primary" id="btnLeadGenerate">Generate lead</button>
</div>
</div>
</div>
<!-- ============ FINDING DETAIL MODAL ============ -->
<div class="modal-overlay" id="findingModal" hidden>
<div class="modal modal-lg">
<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>
<div class="toasts" id="toasts"></div>
<script src="/vendor/xterm.js"></script>
<script src="/vendor/xterm-addon-fit.js"></script>
<script src="/app.js"></script>
</body>
</html>