Files
NeuroSploit/web/public/index.html
T
CyberSecurityUPandClaude Opus 4.6 ecced9d713 feat(web): per-finding attack path, LLM config modal, severity badges, cloud mode
- Graph tab: sidebar with per-finding filter + Overview; description panel
  shows finding details when selected; walk-based subgraph isolation
- Settings: single Config button opens modal with all LLM providers;
  removed redundant MCP Server card
- Issues: colored severity badges (critical/high/medium/low/info)
- Sidebar: removed key and terminal icons from footer
- Server: cloud environment overrides, vault & guardrails config endpoints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqW6BbQgqGkEDn3RYuCVoM
2026-10-16 17:25:24 -03:00

1179 lines
67 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.3.2 — Console</title>
<link rel="icon" type="image/svg+xml" href="logo.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">
<svg class="mark" viewBox="0 0 48 48" fill="none" aria-hidden="true">
<path d="M9 39V11l20 20V11" stroke="currentColor" stroke-width="5.5" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M39 9V37" stroke="currentColor" stroke-width="5.5" stroke-linecap="square"/>
<g stroke="#22a8ff" stroke-width="2.4" fill="#22a8ff">
<path d="M12 16h7l6 6h8" fill="none"/>
<circle cx="11" cy="16" r="3.4"/><circle cx="34" cy="22" r="3.4"/>
<circle cx="38" cy="10" r="3.4"/><circle cx="15" cy="37" r="3.4"/>
<path d="M18 34l-3 3" fill="none"/>
</g>
<path d="M41 7L18 41" stroke="#22a8ff" stroke-width="2.6" stroke-linecap="round"/>
</svg>
NeuroSploit
</div>
<button class="icon-btn" id="btnThemeToggle" title="Toggle light / dark theme">☾</button>
</div>
<button class="sb-new" id="btnNewEngagement">+ New Pentest</button>
<nav class="sb-nav" id="sbNav">
<button class="sb-nav-item active" data-view="dash"><span class="sb-nav-icon">▤</span> Dashboard</button>
<button class="sb-nav-item" data-view="pentests"><span class="sb-nav-icon">⊞</span> Pentests</button>
<button class="sb-nav-item" data-view="prreviews"><span class="sb-nav-icon">⎇</span> PR Reviews</button>
<button class="sb-nav-item" data-view="issues"><span class="sb-nav-icon">⚑</span> Issues</button>
<div class="sb-nav-separator"></div>
<button class="sb-nav-item" data-view="repos"><span class="sb-nav-icon">◎</span> Repositories</button>
<button class="sb-nav-item" data-view="domains"><span class="sb-nav-icon">◇</span> Domains</button>
<button class="sb-nav-item" data-view="networks"><span class="sb-nav-icon">⬡</span> Networks</button>
<div class="sb-nav-separator"></div>
<button class="sb-nav-item" data-view="vault"><span class="sb-nav-icon">🔑</span> Vault</button>
<button class="sb-nav-item" data-view="guardrails"><span class="sb-nav-icon">⛨</span> Guardrails</button>
<div class="sb-nav-separator"></div>
<button class="sb-nav-item" id="sbTerminal"><span class="sb-nav-icon">❭_</span> Terminal</button>
<button class="sb-nav-item" data-view="integrations"><span class="sb-nav-icon">⚙</span> Integrations</button>
<button class="sb-nav-item" data-view="settings"><span class="sb-nav-icon">☰</span> Settings</button>
</nav>
<div class="sb-run-history" id="sbRunHistory">
<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>
<div class="sb-bottom">
<span class="sb-version" id="sbVersion">v4.3.2</span>
</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>
<!-- ============ PENTESTS ============ -->
<section class="page-view" id="pentestsView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">Pentests</div>
<div class="topbar-sub">All engagements — black-box, white-box, greybox, host, AI, mobile, container</div>
</div>
<div class="topbar-spacer"></div>
<input type="text" id="pentestSearch" class="topbar-search" placeholder="Search pentests…" />
<button class="btn btn-accent" id="btnNewPentest2">+ New Pentest</button>
</header>
<div class="page-body" id="pentestsBody"><div class="empty-state">Loading…</div></div>
</section>
<!-- ============ PR REVIEWS ============ -->
<section class="page-view" id="prreviewsView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">PR Reviews</div>
<div class="topbar-sub">Security reviews of pull requests</div>
</div>
<div class="topbar-spacer"></div>
<button class="btn btn-accent" id="btnNewPrReview">+ New Review</button>
</header>
<div class="page-body" id="prreviewsBody"><div class="empty-state">No PR reviews yet. Run <code>neurosploit pr owner/repo 123</code> or use the MCP server.</div></div>
</section>
<!-- ============ ISSUES ============ -->
<section class="page-view" id="issuesView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">Issues</div>
<div class="topbar-sub">All findings across every engagement</div>
</div>
<div class="topbar-spacer"></div>
<select id="issuesSevFilter" class="dash-range">
<option value="">All severities</option>
<option value="critical">Critical</option>
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
</select>
<select id="issuesStatusFilter" class="dash-range">
<option value="">All statuses</option>
<option value="open">Open</option>
<option value="fixed">Fixed</option>
</select>
<input type="text" id="issuesSearch" class="topbar-search" placeholder="Search issues…" />
</header>
<div class="page-body" id="issuesBody"><div class="empty-state">Loading…</div></div>
</section>
<!-- ============ REPOSITORIES ============ -->
<section class="page-view" id="reposView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">Repositories</div>
<div class="topbar-sub">Connected source repositories for white-box, greybox, and PR reviews</div>
</div>
<div class="topbar-spacer"></div>
<button class="btn btn-accent" id="btnAddRepo">+ Add Repository</button>
</header>
<div class="page-body" id="reposBody"><div class="empty-state">No repositories connected yet.</div></div>
</section>
<!-- ============ DOMAINS ============ -->
<section class="page-view" id="domainsView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">Domains</div>
<div class="topbar-sub">Target domains and subdomains for black-box engagements</div>
</div>
<div class="topbar-spacer"></div>
<button class="btn btn-accent" id="btnAddDomain">+ Add Domain</button>
</header>
<div class="page-body" id="domainsBody"><div class="empty-state">No domains configured yet.</div></div>
</section>
<!-- ============ NETWORKS ============ -->
<section class="page-view" id="networksView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">Networks</div>
<div class="topbar-sub">Network ranges and hosts for infrastructure scanning</div>
</div>
<div class="topbar-spacer"></div>
<button class="btn btn-accent" id="btnAddNetwork">+ Add Network</button>
</header>
<div class="page-body" id="networksBody"><div class="empty-state">No networks configured yet.</div></div>
</section>
<!-- ============ VAULT ============ -->
<section class="page-view" id="vaultView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">Vault</div>
<div class="topbar-sub">Target authentication, credentials files, and API collections</div>
</div>
<div class="topbar-spacer"></div>
</header>
<div class="page-body" id="vaultBody">
<div class="config-grid">
<div class="config-card" style="grid-column:1/-1;">
<div class="config-card-title">Target Authentication</div>
<div class="config-card-desc">Credentials used so agents test as a logged-in user. Kept only for this session.</div>
<div class="form-group">
<label for="vaultAuthHeader">Auth header</label>
<input id="vaultAuthHeader" type="text" placeholder="Authorization: Bearer <token> or Cookie: session=..." />
<div class="field-help">Sent to the CLI as an ephemeral creds file.</div>
</div>
<div class="form-group">
<label>Named roles (multi-identity access-control testing)</label>
<div class="role-list" id="vaultRoleList"></div>
<button class="btn btn-sm" id="btnVaultAddRole" 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="config-card" style="grid-column:1/-1;">
<div class="config-card-title">Credentials File</div>
<div class="form-group">
<label for="vaultCredsPath">creds.yaml path (overrides target auth above)</label>
<input id="vaultCredsPath" type="text" placeholder="creds.yaml" />
<div class="field-help">An explicit file on disk — see neurosploit-rs/creds.example.yaml for the schema.</div>
</div>
<div class="form-group">
<label for="vaultCollectionFile">API collection (Postman / OpenAPI / Swagger / HAR)</label>
<input id="vaultCollectionFile" type="file" accept=".json,application/json,.har" multiple />
<input id="vaultCollectionPath" type="text" placeholder="…or a path on disk: file.json or a directory of .json files" />
<div class="field-help">Imports endpoints as known surface — file or directory of .json files.</div>
</div>
</div>
<div class="config-card" style="grid-column:1/-1;">
<div class="config-card-actions"><button class="btn btn-accent" id="btnVaultSave">Save</button></div>
</div>
</div>
</div>
</section>
<!-- ============ GUARDRAILS ============ -->
<section class="page-view" id="guardrailsView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">Guardrails</div>
<div class="topbar-sub">Authorization, scoping, safety controls, environment, and egress configuration</div>
</div>
<div class="topbar-spacer"></div>
</header>
<div class="page-body" id="guardrailsBody">
<div class="config-grid">
<div class="config-card" style="grid-column:1/-1;">
<div class="config-card-title">Capability Token</div>
<div class="form-group">
<label for="grCapToken">Capability token</label>
<input id="grCapToken" type="text" placeholder="ns-cap.v1.…" spellcheck="false" />
<div class="field-status" id="grCapStatus"></div>
<div class="field-help">Signed grant for this engagement: who authorized it, which hosts, which environment, until when. Verified by the harness. Mint with <code>neurosploit capability issue</code>.</div>
</div>
<div class="form-group">
<label for="grInScope">Additional authorized hosts</label>
<input id="grInScope" type="text" placeholder="app.example.com, *.api.example.com, 10.0.0.0/24" />
<div class="field-help">Without this the engagement is authorized against the target and nothing else.</div>
</div>
</div>
<div class="config-card" style="grid-column:1/-1;">
<div class="config-card-title">Scoping &amp; Guardrails <span class="pill" style="font-size:10px;">enforced in code</span></div>
<div class="config-card-desc">The hard scope is the boundary: a request whose host is not listed is refused before it is sent. Exclusions always win. A capability token still caps all of this.</div>
<div class="form-group">
<label for="grScopeHard">Hard scope — the allowlist</label>
<textarea id="grScopeHard" rows="3" placeholder="one per line, or comma-separated&#10;app.example.com&#10;*.staging.example.com"></textarea>
<div class="field-help">Exact host · <code>*.wildcard</code> · <code>CIDR</code> · <code>https://host/path</code> prefix. Empty = nothing extra enforced.</div>
</div>
<div class="field-row">
<div class="form-group">
<label for="grScopeExclude">Exclusions — always win</label>
<textarea id="grScopeExclude" rows="3" placeholder="payments.example.com&#10;admin.example.com"></textarea>
<div class="field-help">Refused even if the allowlist would cover them.</div>
</div>
<div class="form-group">
<label for="grScopeObserve">Observe-only</label>
<textarea id="grScopeObserve" rows="3" placeholder="cdn.example.com&#10;*.thirdparty.example.com"></textarea>
<div class="field-help">May be looked at (recon) but never attacked.</div>
</div>
</div>
<div class="field-row">
<div class="form-group">
<label>State-changing methods</label>
<div class="check-row"><input type="checkbox" id="grDestructive" /> <label for="grDestructive">Allow DELETE / PUT / PATCH</label></div>
<div class="field-help">Off by default — a scan should not change the target's state.</div>
</div>
<div class="form-group">
<label>Test accounts</label>
<div class="check-row"><input type="checkbox" id="grAccounts" checked /> <label for="grAccounts">Allow account creation</label></div>
</div>
<div class="form-group">
<label for="grMaxAccounts">Max accounts</label>
<input class="narrow" id="grMaxAccounts" type="number" min="0" value="3" />
<div class="field-help">0 = unlimited.</div>
</div>
<div class="form-group">
<label for="grRate">Requests / min</label>
<input class="narrow" id="grRate" type="number" min="0" value="240" />
<div class="field-help">Whole engagement. 0 = unlimited.</div>
</div>
</div>
<div class="form-group">
<label for="grForbidden">Forbidden payloads</label>
<textarea id="grForbidden" rows="2" placeholder="delete from&#10;drop table&#10;rm -rf /"></textarea>
<div class="field-help">Substrings NEVER acceptable. Extends built-in defaults.</div>
</div>
<div class="form-group">
<label for="grNotes">Notes — guidance, NOT enforced</label>
<textarea id="grNotes" rows="2" placeholder="SOW-2026-0142; test window 02:00–06:00 UTC"></textarea>
<div class="field-help">Context passed to agents. Prose is not a control.</div>
</div>
</div>
<div class="config-card" style="grid-column:1/-1;">
<div class="config-card-title">Environment &amp; Policy</div>
<div class="field-row">
<div class="form-group">
<label for="grEnv">Environment</label>
<select id="grEnv">
<option value="production" selected>production</option>
<option value="staging">staging</option>
<option value="development">development</option>
<option value="lab">lab</option>
<option value="ot-production">ot-production (ICS/SCADA)</option>
</select>
<div class="field-help">Scales every risk score.</div>
</div>
<div class="form-group">
<label for="grPolicy">Policy profile</label>
<select id="grPolicy">
<option value="web" selected>web — standard</option>
<option value="ot">ot — read-only, paced, industrial writes blocked</option>
</select>
<div class="field-help">OT blocks writes, disruptive actions, fuzzing and exploit payloads over industrial protocols.</div>
</div>
</div>
</div>
<div class="config-card" style="grid-column:1/-1;">
<div class="config-card-title">Egress &amp; Out-of-band</div>
<div class="form-group">
<label for="grTransport">Transport (how traffic reaches the target)</label>
<input id="grTransport" type="text" placeholder="direct · socks5://127.0.0.1:1080 · openvpn:/path/client.ovpn" spellcheck="false" />
<div class="field-help">An internal target with no transport is refused, not tested.</div>
</div>
<div class="field-row">
<div class="form-group">
<label for="grOobDomain">Out-of-band domain</label>
<input id="grOobDomain" type="text" placeholder="oob.yourdomain.com" spellcheck="false" />
<div class="field-help">Without it blind SSRF/XXE/RCE can only be reported as leads.</div>
</div>
<div class="form-group">
<label for="grOobHttp">OOB HTTP listener</label>
<input id="grOobHttp" type="text" placeholder="0.0.0.0:8080" spellcheck="false" />
<div class="field-help">Where callbacks land.</div>
</div>
<div class="form-group">
<label for="grOobDns">OOB DNS listener</label>
<input id="grOobDns" type="text" placeholder="0.0.0.0:5353" spellcheck="false" />
<div class="field-help">Only if the zone is delegated here.</div>
</div>
</div>
<div class="form-group">
<label for="grSms">Inbound SMS (OTP &amp; rate-limit work)</label>
<input id="grSms" type="text" placeholder="twilio:<sid>:<token>:+15550101 · webhook:https://…/inbox:+15550101" spellcheck="false" />
<div class="field-help">A throttling claim counts delivered messages carrying distinct codes, not HTTP 200s.</div>
</div>
</div>
<div class="config-card" style="grid-column:1/-1;">
<div class="config-card-actions"><button class="btn btn-accent" id="btnGuardrailsSave">Save</button></div>
</div>
</div>
</div>
</section>
<!-- ============ INTEGRATIONS ============ -->
<section class="page-view" id="integrationsView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">Integrations</div>
<div class="topbar-sub">Connect external services — GitHub, GitLab, Jira, Slack, CI/CD, AWS, GCP, Azure</div>
</div>
<div class="topbar-spacer"></div>
</header>
<div class="page-body" id="integrationsBody"><div class="empty-state">Loading…</div></div>
</section>
<!-- ============ SETTINGS ============ -->
<section class="page-view" id="settingsView" hidden>
<header class="topbar">
<div>
<div class="topbar-title">Settings</div>
<div class="topbar-sub">Console configuration, LLM provider keys, and preferences</div>
</div>
<div class="topbar-spacer"></div>
</header>
<div class="page-body" id="settingsBody"><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 Pentest</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>
<button class="mode-tile" data-mode="cloud"><span class="t">Cloud</span><span class="d">AWS / GCP / Azure audit</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 class="field-group" id="fieldCloudGroup" hidden>
<label class="field-label">Connected cloud</label>
<select id="fieldCloudProvider" class="narrow">
<option value="">Select a cloud provider…</option>
</select>
<div class="field-help">Pick a connected cloud integration from the Integrations page. Configure credentials there first.</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="fieldClasses">Focus vuln classes <span class="req">— optional</span></label>
<input id="fieldClasses" type="text" placeholder="idor, sqli, xss, ssrf — pins the matching agents, skips recon-driven selection" />
<div class="field-help">Same as the CLI <code>--class</code> / REPL <code>/class</code>. Leave empty for recon-driven selection.</div>
</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">
<select class="input-sm" id="catFilter" title="Show one category"><option value="">All categories</option></select>
<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>
<div class="section-title">Reasoning budget</div>
<div class="section-desc">Optional. Left on <em>unlimited</em>, the run behaves exactly as it always has — full depth, no cap.</div>
</div>
<div class="check-col">
<div class="check-row"><input type="checkbox" id="fieldResearch" /> <label for="fieldResearch"><b>🔬 Research mode</b> — hunt a novel, CVE-reportable bug (whitebox/greybox)</label></div>
<div class="field-help check-help">Known-CVE de-dup + patch-diff variant analysis. Best with a source repo; reports only genuinely new issues (or a concrete patch bypass).</div>
<div class="check-row check-quick"><input type="checkbox" id="fieldQuick" /> <label for="fieldQuick"><b>⚡ Quick mode</b> — short, low-cost test</label></div>
<div class="field-help check-help">Economy preset: 1 voter, 1 chain round, light recon, ≤6 agents, eco budget. The big token saver. Wins over the settings below.</div>
</div>
<div class="field-row">
<div class="field-group"><label class="field-label" for="fieldBudget">Budget mode</label>
<select class="narrow" id="fieldBudget">
<option value="unlimited" selected>unlimited · full run (default)</option>
<option value="eco">eco · cover ground, reason only on strong signal</option>
<option value="balanced">balanced · investigate what looks suspicious</option>
<option value="aggressive">aggressive · multiple hypotheses, deep validation</option>
</select>
<div class="field-help">How compute is spent, not how much there is.</div>
</div>
<div class="field-group"><label class="field-label" for="fieldTokenLimit">Token limit</label><input class="narrow" id="fieldTokenLimit" type="number" min="0" step="10000" placeholder="none" /><div class="field-help">Hard ceiling for the run. Empty or 0 = none.</div></div>
<div class="field-group"><label class="field-label" for="fieldOrder">Order</label>
<select class="narrow" id="fieldOrder">
<option value="coverage-first" selected>coverage-first</option>
<option value="depth-first">depth-first</option>
</select>
<div class="field-help">Map everything first, or chase a lead as it appears.</div>
</div>
<div class="field-group"><label class="field-label" for="fieldSampleRoute">Sample / route</label><input class="narrow" id="fieldSampleRoute" type="number" min="1" max="50" value="3" /><div class="field-help">Requests per endpoint family (<code>/api/users/{id}</code> is sampled, not enumerated).</div></div>
</div>
<div>
<div class="section-title">Tooling &amp; assurance</div>
<div class="section-desc">Optional. Route through a proxy, run in a container, re-check every PoC, and frame findings against a compliance framework.</div>
</div>
<div class="field-row">
<div class="field-group"><label class="field-label" for="fieldIntercept">Intercepting proxy</label>
<select class="narrow" id="fieldIntercept">
<option value="off" selected>off · direct</option>
<option value="own">NeuroSploit Interceptor (record + passive discovery)</option>
<option value="burp">Burp Suite</option>
<option value="caido">Caido</option>
<option value="zap">OWASP ZAP</option>
<option value="mitmproxy">mitmproxy</option>
<option value="own+burp">NeuroSploit Interceptor + Burp</option>
<option value="own+caido">NeuroSploit Interceptor + Caido</option>
</select>
<div class="field-help">The harness and agent commands route through it. Full HTTPS interception needs one of the tools (own tunnels TLS).</div>
</div>
<div class="field-group"><label class="field-label" for="fieldSandbox">Sandbox</label>
<select class="narrow" id="fieldSandbox">
<option value="" selected>host (no container)</option>
<option value="default">Kali container (kalilinux/kali-rolling)</option>
</select>
<div class="field-help">Runs attack commands off the host, with the Kali toolbox. Needs docker or podman.</div>
</div>
<div class="field-group"><label class="field-label">PoC re-validation</label>
<div class="check-row"><input type="checkbox" id="fieldRevalidatePoc" /> <label for="fieldRevalidatePoc">Re-run every PoC; demote what no longer reproduces</label></div>
</div>
<div class="field-group"><label class="field-label" for="fieldTypesafe">TypeSafe (System One)</label>
<select class="narrow" id="fieldTypesafe">
<option value="auto" selected>auto — on if TYPESAFE_API_KEY set</option>
<option value="on">on — calibrated confirmation + adjudication</option>
<option value="off">off — same pipeline, no TypeSafe (for A/B)</option>
</select>
<div class="field-help">Additional confirmation strategy (Choice/Noul over real replays). Off runs the identical pipeline so you can compare with/without.</div>
</div>
</div>
<div class="field-group">
<label class="field-label">Compliance mapping</label>
<div class="check-row"><input type="checkbox" id="fieldCompPci" value="pci-dss" /> <label for="fieldCompPci">PCI-DSS v4.0</label></div>
<div class="check-row"><input type="checkbox" id="fieldCompHipaa" value="hipaa" /> <label for="fieldCompHipaa">HIPAA Security Rule</label></div>
<div class="check-row"><input type="checkbox" id="fieldCompSoc2" value="soc2" /> <label for="fieldCompSoc2">SOC 2 (Trust Services Criteria)</label></div>
<div class="check-row"><input type="checkbox" id="fieldCompGdpr" value="gdpr" /> <label for="fieldCompGdpr">GDPR (data-protection articles)</label></div>
<div class="field-help">Maps confirmed findings onto control requirements in the report. Indicates gaps for an assessor — never a compliance verdict.</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" id="btnPauseRun" title="Hold the run where it is — in-flight agents finish, nothing is lost">⏸ Pause</button>
<a class="btn" id="btnDownloadLog" download>Download log</a>
<button class="btn" id="btnReportNow" title="Write the report from what has been found so far">Report so far</button>
<button class="btn btn-danger" id="btnStopRun">Stop</button>
<button class="btn" id="btnBackToBoard">← Back</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>
<div class="agent-panel" id="agentPanel" hidden>
<div class="agent-panel-head">
<span>What's being tested</span>
<span class="agent-legend"><i class="ap running"></i>testing <i class="ap done"></i>done <i class="ap found"></i>found <i class="ap failed"></i>error</span>
</div>
<div class="agent-grid" id="agentGrid"></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="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="review_status">Status</th><th data-sort="wire">Wire</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 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>
<a class="btn" id="detailOpenPdf" target="_blank" hidden>⤓ PDF</a>
<button class="btn" id="btnBuildReport" title="Regenerate this run's report from its findings">Generate report</button>
<button class="btn" id="btnGraphRecompute" title="Recompute the attack graph with the CLI (neurosploit graph)">↻ Graph</button>
<button class="btn" id="btnRetest" title="Re-run every finding's recorded proof (neurosploit poc) and see what still reproduces">⟳ Re-test</button>
<a class="btn" id="detailOpenAudit" target="_blank" hidden title="Every action this run took, hash-chained">Audit trail</a>
<button class="btn btn-danger" id="btnDeleteRun" title="Permanently delete this session and its report">🗑 Delete</button>
<button class="btn" id="btnDetailBack">← Back</button>
</div>
</header>
<nav class="run-tabs">
<button class="run-tab active" data-tab="findings">Findings <span id="detailFindingsCount">0</span></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>
<button class="btn btn-sm" id="btnExportCsv" title="Export the filtered findings as CSV">⤓ CSV</button>
</div>
<div class="kpi-row" id="detailKpis"></div>
<div id="detailRegression" hidden></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="review_status">Status</th><th data-sort="wire">Wire</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>
</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">LLM API keys</button>
<button class="modal-tab" data-mtab="creds">Creds file</button>
<button class="modal-tab" data-mtab="authz">Authorization</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="section-title">Model provider keys</div>
<div class="field-help" style="margin:4px 0 12px;">The API keys for the <b>LLMs that run the engagement</b> (not the target's auth — that's the <em>Target auth</em> tab). 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="authz" hidden>
<div class="field-group">
<label class="field-label" for="capToken">Capability token</label>
<input id="capToken" type="text" placeholder="ns-cap.v1.…" spellcheck="false" />
<div class="field-status" id="capStatus"></div>
<div class="field-help">
The signed grant for this engagement: who authorized it, which hosts, which environment, until when.
It is verified by the harness (<code>NEUROSPLOIT_CAPABILITY_KEY</code>) and acts as a <strong>ceiling</strong> —
the settings below can narrow the scope, never widen it. Mint one with
<code>neurosploit capability issue</code>.
</div>
</div>
<div class="field-group">
<label class="field-label" for="inScope">Additional authorized hosts</label>
<input id="inScope" type="text" placeholder="app.example.com, *.api.example.com, 10.0.0.0/24" />
<div class="field-help">Without this the engagement is authorized against the target and nothing else — discovering a host is not permission to test it.</div>
</div>
<div class="section-title" style="margin-top:24px;margin-bottom:8px;display:flex;align-items:center;gap:8px;">
Scoping &amp; Guardrails
<span class="pill" style="font-size:10px;">enforced in code</span>
</div>
<div class="field-help" style="margin:0 0 14px;">
The <b>hard scope</b> is the boundary: a request whose host is not listed is <b>refused before it is sent</b> — not warned about. Exclusions always win. A capability token still caps all of this. Leave the hard list empty to keep the plain target + <em>Additional authorized hosts</em> behaviour.
</div>
<div class="field-group">
<label class="field-label" for="scopeHard">Hard scope <span class="req">— the allowlist</span></label>
<textarea id="scopeHard" rows="3" placeholder="one per line, or comma-separated&#10;app.example.com&#10;*.staging.example.com&#10;https://example.com/api/v2&#10;10.20.30.0/24"></textarea>
<div class="field-help">Exact host · <code>*.wildcard</code> (apex + subdomains) · <code>CIDR</code> · <code>https://host/path</code> prefix. Empty = nothing extra is enforced here.</div>
</div>
<div class="field-row">
<div class="field-group">
<label class="field-label" for="scopeExclude">Exclusions <span class="req">— always win</span></label>
<textarea id="scopeExclude" rows="3" placeholder="payments.example.com&#10;admin.example.com&#10;https://app.example.com/billing"></textarea>
<div class="field-help">Refused even if the allowlist would cover them.</div>
</div>
<div class="field-group">
<label class="field-label" for="scopeObserve">Observe-only</label>
<textarea id="scopeObserve" rows="3" placeholder="cdn.example.com&#10;*.thirdparty.example.com"></textarea>
<div class="field-help">May be looked at (recon) but never attacked.</div>
</div>
</div>
<div class="field-row">
<div class="field-group">
<label class="field-label">State-changing methods</label>
<div class="check-row"><input type="checkbox" id="scopeDestructive" /> <label for="scopeDestructive">Allow DELETE / PUT / PATCH</label></div>
<div class="field-help">Off by default — a scan should not change the target's state to prove a bug.</div>
</div>
<div class="field-group">
<label class="field-label">Test accounts</label>
<div class="check-row"><input type="checkbox" id="scopeAccounts" checked /> <label for="scopeAccounts">Allow account creation</label></div>
</div>
<div class="field-group">
<label class="field-label" for="scopeMaxAccounts">Max accounts</label>
<input class="narrow" id="scopeMaxAccounts" type="number" min="0" value="3" />
<div class="field-help">0 = unlimited.</div>
</div>
<div class="field-group">
<label class="field-label" for="scopeRate">Requests / min</label>
<input class="narrow" id="scopeRate" type="number" min="0" value="240" />
<div class="field-help">Whole engagement. 0 = unlimited. Keep low on production.</div>
</div>
</div>
<div class="field-group">
<label class="field-label" for="scopeForbidden">Forbidden payloads</label>
<textarea id="scopeForbidden" rows="2" placeholder="delete from&#10;drop table&#10;rm -rf /"></textarea>
<div class="field-help">Substrings NEVER acceptable, whatever the finding — the classes that damage a target instead of demonstrating a bug. Extends the built-in defaults.</div>
</div>
<div class="field-group">
<label class="field-label" for="scopeNotes">Notes <span class="req">— guidance, NOT enforced</span></label>
<textarea id="scopeNotes" rows="2" placeholder="SOW-2026-0142; test window 02:00–06:00 UTC; prove PII with a canary row only"></textarea>
<div class="field-help">Context passed to the agents. Kept separate from the rules on purpose — prose is not a control.</div>
</div>
<div class="field-row">
<div class="field-group">
<label class="field-label" for="envSelect">Environment</label>
<select id="envSelect">
<option value="production" selected>production</option>
<option value="staging">staging</option>
<option value="development">development</option>
<option value="lab">lab</option>
<option value="ot-production">ot-production (ICS/SCADA)</option>
</select>
<div class="field-help">Scales every risk score — the same action is a different act on a lab bench and on a live substation.</div>
</div>
<div class="field-group">
<label class="field-label" for="policySelect">Policy profile</label>
<select id="policySelect">
<option value="web" selected>web — standard</option>
<option value="ot">ot — read-only, paced, industrial writes blocked</option>
</select>
<div class="field-help">OT blocks writes, disruptive actions, fuzzing and exploit payloads over industrial protocols, and caps the rate at ~1 req/s.</div>
</div>
</div>
<div class="section-title" style="margin-top:18px;">Egress &amp; out-of-band</div>
<div class="field-group">
<label class="field-label" for="transportSpec">Transport (how traffic reaches the target)</label>
<input id="transportSpec" type="text" placeholder="direct · socks5://127.0.0.1:1080 · openvpn:/path/client.ovpn · ssh://red@bastion.corp?forward=10.0.0.5:445 · cloudflared://db.internal:5432" spellcheck="false" />
<div class="field-help">
An <strong>internal</strong> target with no transport is <strong>refused</strong>, not tested — with the VPN down,
<code>10.20.0.15</code> belongs to whatever network this host is on, which is not the client's.
</div>
</div>
<div class="field-row">
<div class="field-group">
<label class="field-label" for="oobDomain">Out-of-band domain</label>
<input id="oobDomain" type="text" placeholder="oob.yourdomain.com" spellcheck="false" />
<div class="field-help">A wildcard pointed at this host. Without it blind SSRF/XXE/RCE can only be reported as leads — never confirmed.</div>
</div>
<div class="field-group">
<label class="field-label" for="oobHttp">OOB HTTP listener</label>
<input id="oobHttp" type="text" placeholder="0.0.0.0:8080" spellcheck="false" />
<div class="field-help">Where callbacks land. An HTTP callback proves egress; a DNS query alone does not.</div>
</div>
<div class="field-group">
<label class="field-label" for="oobDns">OOB DNS listener</label>
<input id="oobDns" type="text" placeholder="0.0.0.0:5353" spellcheck="false" />
<div class="field-help">Only if the zone is delegated here (NS record).</div>
</div>
</div>
<div class="field-group">
<label class="field-label" for="smsSpec">Inbound SMS (OTP &amp; rate-limit work)</label>
<input id="smsSpec" type="text" placeholder="twilio:&lt;sid&gt;:&lt;token&gt;:+15550101 · webhook:https://…/inbox:+15550101" spellcheck="false" />
<div class="field-help">A throttling claim counts <strong>delivered messages carrying distinct codes</strong>, not HTTP 200s.</div>
</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 class="field-group">
<label class="field-label" for="collectionFile">API collection (Postman / OpenAPI / Swagger / HAR)</label>
<input id="collectionFile" type="file" accept=".json,application/json,.har" multiple />
<input id="collectionPath" type="text" placeholder="…or a path on disk: file.json or a directory of .json files" />
<div class="field-help" id="collectionHelp">Imports endpoints as known surface — file or directory of .json files. Every route is tested directly, alongside recon.</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-help" style="margin-bottom:14px;">A <b>lead</b> is a specialist agent the harness runs. Add one two ways — let Claude write it from a description, or paste your own agent / system prompt to run verbatim. Either way it is saved as a real, pinnable agent in <code>agents_md/vulns/</code> and you can remove it later.</div>
<div class="seg-tabs" id="leadModeTabs">
<button class="seg-tab active" data-leadmode="describe">Describe it</button>
<button class="seg-tab" data-leadmode="own">Paste my own</button>
</div>
<div class="field-group" data-leadpanel="describe">
<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) writes a real specialist agent for it and pins it. If generation is unavailable, the text is folded into <code>--focus</code> instead.</div>
</div>
<div class="field-group" data-leadpanel="own" hidden>
<label class="field-label" for="leadName">Name</label>
<input id="leadName" type="text" placeholder="e.g. GraphQL Introspection Abuse" />
<label class="field-label" for="leadOwn" style="margin-top:10px;">Your agent / system prompt</label>
<textarea id="leadOwn" rows="10" placeholder="Paste a full agent markdown (# Title / ## User Prompt / ## System Prompt), or just your system prompt — it will be wrapped into an agent. Use {target} and {recon_json} placeholders."></textarea>
<div class="field-help">Used verbatim — no model rewrites it. {target} and {recon_json} are filled per run.</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">Add 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" id="fmSection-wire">
<div class="fm-wire-head">
<label class="field-label">Wire evidence — the bytes behind this finding</label>
<button class="btn btn-sm" id="btnFindingRetest">⟳ Re-test this finding</button>
</div>
<div id="fmWireSummary"></div>
<div id="fmRetestHistory"></div>
<div id="fmWireExchanges" class="wire-exchanges"></div>
</div>
<div class="field-group">
<label class="field-label">Other scripts from this run</label>
<div id="fmPocList" class="field-help">—</div>
</div>
</div>
</div>
</div>
<!-- ============ ADD REPOSITORY MODAL ============ -->
<div class="modal-overlay" id="repoModal" hidden>
<div class="modal modal-sm">
<div class="modal-head">
<div class="title">Add Repository</div>
<button class="icon-btn" onclick="$('#repoModal').hidden=true">✕</button>
</div>
<div class="modal-body">
<div class="field-group">
<label class="field-label" for="repoUrl">Repository URL or path</label>
<input id="repoUrl" type="text" placeholder="https://github.com/owner/repo or /path/to/repo" spellcheck="false" />
<div class="field-help">GitHub URL, owner/repo shorthand, GitLab URL, or a local path.</div>
</div>
<div class="field-group">
<label class="field-label" for="repoName">Display name (optional)</label>
<input id="repoName" type="text" placeholder="My API" />
</div>
<div class="field-group">
<label class="field-label" for="repoBranch">Default branch</label>
<input id="repoBranch" type="text" placeholder="main" value="main" />
</div>
<div class="field-group">
<label class="field-label" for="repoDesc">Description (optional)</label>
<input id="repoDesc" type="text" placeholder="Production API service" />
</div>
</div>
<div class="modal-foot">
<button class="btn" onclick="$('#repoModal').hidden=true">Cancel</button>
<button class="btn btn-primary" id="btnRepoSubmit">Add Repository</button>
</div>
</div>
</div>
<!-- ============ ADD DOMAIN MODAL ============ -->
<div class="modal-overlay" id="domainModal" hidden>
<div class="modal modal-sm">
<div class="modal-head">
<div class="title">Add Domain</div>
<button class="icon-btn" onclick="$('#domainModal').hidden=true">✕</button>
</div>
<div class="modal-body">
<div class="field-group">
<label class="field-label" for="domainName">Domain</label>
<input id="domainName" type="text" placeholder="app.example.com" spellcheck="false" />
<div class="field-help">The target domain for black-box scanning. Subdomains will be discovered during recon.</div>
</div>
<div class="field-group">
<label class="field-label" for="domainType">Type</label>
<select id="domainType">
<option value="web" selected>Web application</option>
<option value="api">API</option>
<option value="llm">LLM endpoint</option>
<option value="mobile">Mobile backend</option>
<option value="other">Other</option>
</select>
</div>
<div class="field-group">
<label class="field-label" for="domainDesc">Description (optional)</label>
<input id="domainDesc" type="text" placeholder="Production web application" />
</div>
<div class="field-group">
<label class="field-label" for="domainScope">Scope</label>
<select id="domainScope">
<option value="in-scope" selected>In scope</option>
<option value="observe-only">Observe only</option>
</select>
</div>
</div>
<div class="modal-foot">
<button class="btn" onclick="$('#domainModal').hidden=true">Cancel</button>
<button class="btn btn-primary" id="btnDomainSubmit">Add Domain</button>
</div>
</div>
</div>
<!-- ============ ADD NETWORK MODAL ============ -->
<div class="modal-overlay" id="networkModal" hidden>
<div class="modal modal-sm">
<div class="modal-head">
<div class="title">Add Network</div>
<button class="icon-btn" onclick="$('#networkModal').hidden=true">✕</button>
</div>
<div class="modal-body">
<div class="field-group">
<label class="field-label" for="networkCidr">CIDR or host</label>
<input id="networkCidr" type="text" placeholder="10.0.0.0/24 or 192.168.1.10" spellcheck="false" />
<div class="field-help">Network range in CIDR notation, or a single IP/hostname.</div>
</div>
<div class="field-group">
<label class="field-label" for="networkName">Name (optional)</label>
<input id="networkName" type="text" placeholder="Corporate LAN" />
</div>
<div class="field-group">
<label class="field-label" for="networkDesc">Description (optional)</label>
<input id="networkDesc" type="text" placeholder="Internal network segment" />
</div>
<div class="field-group">
<label class="field-label" for="networkTransport">Transport</label>
<input id="networkTransport" type="text" placeholder="direct / socks5://host:port / ssh://user@bastion" spellcheck="false" />
<div class="field-help">How to reach this network. Leave empty for direct access.</div>
</div>
</div>
<div class="modal-foot">
<button class="btn" onclick="$('#networkModal').hidden=true">Cancel</button>
<button class="btn btn-primary" id="btnNetworkSubmit">Add Network</button>
</div>
</div>
</div>
<!-- ============ INTEGRATION CONFIG MODAL ============ -->
<div class="modal-overlay" id="integrationModal" hidden>
<div class="modal modal-sm">
<div class="modal-head">
<div class="title" id="integModalTitle">Configure Integration</div>
<button class="icon-btn" onclick="$('#integrationModal').hidden=true">✕</button>
</div>
<div class="modal-body" id="integModalBody">
</div>
<div class="modal-foot">
<button class="btn" onclick="$('#integrationModal').hidden=true">Cancel</button>
<button class="btn btn-primary" id="btnIntegSubmit">Save</button>
</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>