From ccc13cf9336cc72e1a53f2027fec8e9f06f6a78e Mon Sep 17 00:00:00 2001 From: CyberSecurityUP Date: Sun, 18 Oct 2026 23:30:46 -0300 Subject: [PATCH] fix(harness,web): preserve rejected findings and add search filters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pipeline: keep ALL candidate findings after validation instead of discarding vote-rejected ones. Rejected findings are marked with review_status="rejected" so the UI can distinguish them visually. Previously 11 findings during a run would shrink to 2 after validation — now all 13 appear with their status clearly indicated. Web console: implement working search/filter for Pentests list (#pentestSearch) and Issues page (#issuesSearch, severity dropdown, status dropdown). Previously the search inputs existed in HTML but had no event listeners — typing in them did nothing. --- neurosploit-rs/crates/harness/src/pipeline.rs | 17 +++- web/public/app.js | 93 +++++++++++++------ 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/neurosploit-rs/crates/harness/src/pipeline.rs b/neurosploit-rs/crates/harness/src/pipeline.rs index d579311..9e27917 100644 --- a/neurosploit-rs/crates/harness/src/pipeline.rs +++ b/neurosploit-rs/crates/harness/src/pipeline.rs @@ -2735,14 +2735,21 @@ async fn validate_with_persist(candidates: Vec, pool: &ModelPool, sys: .buffer_unordered(pool.candidates.len().max(2)) .collect() .await; - // Keep confirmed AND needs-review (human decides); drop only zero-support noise. - // Persist vote-rejected findings for operator refinement. - let (kept, vote_rejected): (Vec<_>, Vec<_>) = validated.into_iter() - .partition(|f: &Finding| f.validated || f.review_status == "needs-review"); + // Keep ALL findings — confirmed, needs-review, AND vote-rejected. Operators + // want to see what agents found even if the validation panel did not confirm + // it. Mark rejected ones so the UI can distinguish them visually. + let all: Vec = validated.into_iter().map(|mut f| { + if !f.validated && f.review_status.is_empty() { + f.review_status = "rejected".into(); + f.review_reason = "vote-rejected by validation panel".into(); + } + f + }).collect(); + let vote_rejected: Vec = all.iter().filter(|f| f.review_status == "rejected").cloned().collect(); if !vote_rejected.is_empty() { persist_rejected(workdir, &vote_rejected); } - flagged.extend(kept); + flagged.extend(all); flagged } diff --git a/web/public/app.js b/web/public/app.js index 06ef80e..75bb8ab 100644 --- a/web/public/app.js +++ b/web/public/app.js @@ -3630,23 +3630,37 @@ async function loadPentests() { try { const runs = await api('/api/runs'); if (!runs.length) { body.innerHTML = '
No pentests yet. Click + New Pentest to start.
'; return; } - const rows = runs.map(r => { - const mode = (r.id || '').startsWith('ns-wb-') ? 'whitebox' : (r.id || '').startsWith('ns-gb-') ? 'greybox' : (r.id || '').startsWith('ns-host-') ? 'host' : 'blackbox'; - const when = r.when ? timeAgo(Math.floor(new Date(r.when).getTime() / 1000)) : ''; - return ` - ${esc(r.id)} - ${esc(r.target || r.name || '')} - ${esc(mode)} - ${r.findings ?? 0} - ${esc(r.state || 'done')} - ${when} - `; - }).join(''); - body.innerHTML = `${rows}
IDTargetModeFindingsStatusWhen
`; - $$('.pentest-row', body).forEach(row => { - row.style.cursor = 'pointer'; - row.addEventListener('click', () => { const run = runs.find(r => r.id === row.dataset.id); if (run) openRun(run); }); - }); + const renderRows = (q) => { + const lq = (q || '').trim().toLowerCase(); + const filtered = lq ? runs.filter(r => { + const blob = `${r.id || ''} ${r.target || ''} ${r.name || ''} ${r.state || ''}`.toLowerCase(); + return blob.includes(lq); + }) : runs; + const rows = filtered.map(r => { + const mode = (r.id || '').startsWith('ns-wb-') ? 'whitebox' : (r.id || '').startsWith('ns-gb-') ? 'greybox' : (r.id || '').startsWith('ns-host-') ? 'host' : 'blackbox'; + const when = r.when ? timeAgo(Math.floor(new Date(r.when).getTime() / 1000)) : ''; + return ` + ${esc(r.id)} + ${esc(r.target || r.name || '')} + ${esc(mode)} + ${r.findings ?? 0} + ${esc(r.state || 'done')} + ${when} + `; + }).join(''); + body.innerHTML = rows + ? `${rows}
IDTargetModeFindingsStatusWhen
` + : `
No pentests matching "${esc(q)}".
`; + $$('.pentest-row', body).forEach(row => { + row.style.cursor = 'pointer'; + row.addEventListener('click', () => { const run = runs.find(r => r.id === row.dataset.id); if (run) openRun(run); }); + }); + }; + renderRows($('#pentestSearch')?.value || ''); + const searchInput = $('#pentestSearch'); + if (searchInput) { + searchInput.oninput = () => renderRows(searchInput.value); + } } catch (e) { body.innerHTML = `
Failed to load: ${esc(e.message)}
`; } } @@ -3671,18 +3685,41 @@ async function loadIssues() { try { const data = await api('/api/issues'); if (!data.length) { body.innerHTML = '
No findings across any engagement yet.
'; return; } - const rows = data.map(f => { - const sc = sevClass(f.severity); - return `${esc(f.severity)}${esc(f.title)}${esc(f.target || '')}${esc(f.run || '')}${esc(f.status || 'open')}`; - }).join(''); - body.innerHTML = `${rows}
SeverityTitleTargetRunStatus
`; - $$('.issue-row', body).forEach(row => { - row.addEventListener('click', () => { - const runId = row.dataset.run; - if (!runId) return; - openContributorFinding(runId, row.dataset.title, row.dataset.cwe); + const renderRows = () => { + const q = ($('#issuesSearch')?.value || '').trim().toLowerCase(); + const sevFilter = ($('#issuesSevFilter')?.value || '').toLowerCase(); + const statusFilter = ($('#issuesStatusFilter')?.value || '').toLowerCase(); + const filtered = data.filter(f => { + if (sevFilter && (f.severity || '').toLowerCase() !== sevFilter) return false; + if (statusFilter && (f.status || 'open').toLowerCase() !== statusFilter) return false; + if (q) { + const blob = `${f.title || ''} ${f.target || ''} ${f.cwe || ''} ${f.run || ''} ${f.severity || ''} ${f.agent || ''}`.toLowerCase(); + if (!blob.includes(q)) return false; + } + return true; }); - }); + const rows = filtered.map(f => { + const sc = sevClass(f.severity); + return `${esc(f.severity)}${esc(f.title)}${esc(f.target || '')}${esc(f.run || '')}${esc(f.status || 'open')}`; + }).join(''); + body.innerHTML = rows + ? `${rows}
SeverityTitleTargetRunStatus
` + : '
No issues matching current filters.
'; + $$('.issue-row', body).forEach(row => { + row.addEventListener('click', () => { + const runId = row.dataset.run; + if (!runId) return; + openContributorFinding(runId, row.dataset.title, row.dataset.cwe); + }); + }); + }; + renderRows(); + const searchInput = $('#issuesSearch'); + if (searchInput) searchInput.oninput = renderRows; + const sevSelect = $('#issuesSevFilter'); + if (sevSelect) sevSelect.onchange = renderRows; + const statusSelect = $('#issuesStatusFilter'); + if (statusSelect) statusSelect.onchange = renderRows; } catch (e) { body.innerHTML = `
Failed to load: ${esc(e.message)}
`; } }