Files
NeuroSploit/web/public/index.html
T
CyberSecurityUPandClaude Opus 5 4f277838c6 fix(web): responsive pass — real device sizes, scroll in the right containers
Audited at 390×844, 844×390 (phone landscape), 768×1024, 1024×768, 1440×900
and ≥1600px. What was actually broken:

- **The run header collapsed.** `justify-content: space-between` let the two
  action buttons take the whole row, so at 390px the title wrapped one
  character per line ("Test AspNe / t") and the facts line wrapped one word per
  line. It now stacks below 680px, with the title block sized to its content
  instead of stretching (the desktop `flex: 1` was what left a 180px void above
  the buttons in the stacked layout).
- **The terminal header overflowed** its dock at 390px (557px of content in a
  390px box) — it wraps now, and the status text drops out on narrow screens
  where the coloured dot already carries it.
- **`100vh` is wrong on mobile.** It measures the viewport without the
  collapsing address bar, so the wizard footer and its CTA sit underneath it.
  Switched to `dvh` with the `vh` line kept as the fallback.
- **The dock took 82% of a phone in landscape** at its fixed 320px. It now
  tracks the viewport (`clamp(180px, 42dvh, 340px)`, tighter still under
  500px of height).
- **The off-canvas drawer had no way out but the button that opened it.**
  Added a scrim that closes it, Esc, and auto-close when a run is picked —
  and it closes itself if the window grows past the breakpoint, which
  otherwise left a scrim over a sidebar that was no longer a drawer.
- **The stepper scrolls horizontally on a phone**, so advancing to an
  off-screen step looked like nothing happened; the active step is scrolled
  into view.

Device-type rules rather than width alone: `pointer: coarse` gets 38-44px hit
targets and 16px inputs (under 16px, iOS zooms the page on focus and breaks the
layout the user is typing into); `prefers-reduced-motion` drops the drawer
slide and the progress animation, which are decoration.

Scrolling stays where it belongs — one scroller per pane (`.wizard-body`,
`.run-body`, `.dash-body`, `.sb-groups`, `.modal-body`, `.term-host`), wide
tables scroll inside `.table-wrap`, and the page itself never scrolls
horizontally at any tested size.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 15:16:24 -03:00

459 lines
23 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" aria-expanded="false" aria-controls="sidebar">☰</button>
<div class="sidebar-scrim" id="sidebarScrim" hidden></div>
<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>
<button class="sb-link" id="btnDashboard">▤ Dashboard</button>
<div class="sb-search">
<span class="search-icon" aria-hidden="true">⌕</span>
<input id="runFilter" type="text" autocomplete="off" placeholder="Filter runs" aria-label="Filter runs" />
</div>
<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">
<!-- ============ DASHBOARD ============ -->
<section class="dashboard" id="dashView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">Dashboard</div>
<div class="topbar-sub">Coverage, findings and FAIR loss exposure across every run</div>
</div>
<div class="topbar-spacer"></div>
<select id="dashRange" class="dash-range" title="Time range">
<option value="0">all time</option>
<option value="7">last 7 days</option>
<option value="30">last 30 days</option>
<option value="90">last 90 days</option>
</select>
<button class="btn" id="btnDashRefresh">Refresh</button>
</header>
<div class="dash-body" id="dashBody">
<div class="empty-state">Loading…</div>
</div>
</section>
<!-- ============ 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>