v3.5.1: live findings + /finding + Ctrl+O/expand + 3-way /stop (soft validate) + report URL + structured Typst + IIS/CMS/CVE agents

REPL interactivity & findings:
- Live findings registered during a run: /results shows them accumulating;
  /finding opens a selection menu with FULL details (PoC, command, evidence,
  CVSS, OWASP/CWE, remediation). Past runs too.
- /expand (and Ctrl+O) dump the last full, untruncated commands.
- Findings colored by severity in the feed (not all-yellow); confirmed vote = green.

Stop & report:
- CRITICAL: /stop no longer kills validation. New SOFT stop (pool.soft) halts
  launching new agents but lets in-flight + VALIDATION finish — so confirmed
  findings are kept. /stop now asks 3 ways: [1] validate then report,
  [2] report raw (no validation), [3] discard.
- Report file:// URL printed on completion/stop.

Report:
- Typst report restructured: executive summary, a Vulnerability Summary TABLE
  (#, vuln, severity, CVSS, OWASP/CWE), and per-finding sections with criticality,
  CVSS, OWASP/CWE, description/impact, PoC, evidence, remediation. owasp passed through.

Agents: +14 app-stack/CVE (IIS tilde/WebDAV/ViewState/debug/handler-bypass,
CMS fingerprint + WordPress/Joomla/Drupal/default-admin, app-server consoles,
exposed VCS, known-CVE & outdated-component exploitation) → 343 total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
CyberSecurityUP
2026-06-24 23:21:43 -03:00
parent df73c0e134
commit eb4e13efea
24 changed files with 906 additions and 33 deletions
@@ -159,7 +159,7 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender<Str
let directives = directives.clone();
let txc = tx.clone();
async move {
if pool.is_cancelled() {
if pool.stop_exploiting() {
return (ag.name.clone(), String::new(), vec![]);
}
if verbose {
@@ -184,6 +184,7 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender<Str
// Live findings feed: surface each candidate the moment it appears.
for c in &f {
let _ = txc.send(format!("finding: [{}] {} @ {}", c.severity, c.title, c.endpoint)).await;
if let Ok(j) = serde_json::to_string(c) { let _ = txc.send(format!("finding_json: {j}")).await; }
}
(ag.name.clone(), text, f)
}
@@ -378,7 +379,7 @@ pub async fn run_greybox(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Se
let leads = leads_ctx.clone();
let txc = tx.clone();
async move {
if pool.is_cancelled() {
if pool.stop_exploiting() {
return (ag.name.clone(), String::new(), vec![]);
}
if verbose {
@@ -931,7 +932,7 @@ pub async fn run_host(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sende
let directives = directives.clone();
let txc = tx.clone();
async move {
if pool.is_cancelled() { return (ag.name.clone(), String::new(), vec![]); }
if pool.stop_exploiting() { return (ag.name.clone(), String::new(), vec![]); }
if verbose {
let _ = txc.send(format!(" ▶ launching agent: {} ({})", ag.name, ag.title.replace(" Agent", ""))).await;
}
@@ -944,7 +945,10 @@ pub async fn run_host(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sende
Ok((m, text)) => {
let f = extract_findings(&text, &ag.name);
let _ = txc.send(format!("test {} via {}{} candidate(s)", ag.name, m.label(), f.len())).await;
for c in &f { let _ = txc.send(format!("finding: [{}] {} @ {}", c.severity, c.title, c.endpoint)).await; }
for c in &f {
let _ = txc.send(format!("finding: [{}] {} @ {}", c.severity, c.title, c.endpoint)).await;
if let Ok(j) = serde_json::to_string(c) { let _ = txc.send(format!("finding_json: {j}")).await; }
}
(ag.name.clone(), text, f)
}
Err(e) => { let _ = txc.send(format!("test {} failed: {e}", ag.name)).await;
+16 -3
View File
@@ -34,9 +34,11 @@ pub struct ModelPool {
/// Progress channel: when set, the subscription CLI streams structured
/// activity (tools called, commands run, files read) here live.
progress: std::sync::Mutex<Option<tokio::sync::mpsc::Sender<String>>>,
/// Cooperative cancellation: when set, in-flight model calls short-circuit
/// and the pipeline stops launching new agents (graceful stop).
/// HARD cancellation: when set, in-flight model calls short-circuit (abort).
cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
/// SOFT stop: stop launching new EXPLOIT agents, but let in-flight finish and
/// VALIDATION still run — so "stop and validate what was found" works.
soft: std::sync::Arc<std::sync::atomic::AtomicBool>,
}
impl ModelPool {
@@ -65,6 +67,7 @@ impl ModelPool {
mcp_config,
progress: std::sync::Mutex::new(None),
cancel: Arc::new(std::sync::atomic::AtomicBool::new(false)),
soft: Arc::new(std::sync::atomic::AtomicBool::new(false)),
}
}
@@ -80,13 +83,23 @@ impl ModelPool {
self.progress.lock().ok().and_then(|g| g.clone())
}
/// Handle to request graceful cancellation of an in-progress engagement.
/// Handle to request HARD cancellation (abort all model calls).
pub fn cancel_handle(&self) -> Arc<std::sync::atomic::AtomicBool> {
self.cancel.clone()
}
/// Handle to request a SOFT stop (stop launching new exploit agents; keep
/// validation running).
pub fn soft_handle(&self) -> Arc<std::sync::atomic::AtomicBool> {
self.soft.clone()
}
pub fn is_cancelled(&self) -> bool {
self.cancel.load(std::sync::atomic::Ordering::Relaxed)
}
/// Should the exploit phase stop launching new agents? (hard OR soft stop)
pub fn stop_exploiting(&self) -> bool {
self.cancel.load(std::sync::atomic::Ordering::Relaxed)
|| self.soft.load(std::sync::atomic::Ordering::Relaxed)
}
/// One completion for a model, via subscription CLI (optionally with MCP) or
/// HTTP API, with a short retry/backoff. `label` (e.g. the agent name) tags
+3 -2
View File
@@ -139,9 +139,10 @@ pub fn typst_report(target: &str, findings: &[Finding], dir: &Path) -> std::io::
));
data.push_str("#let findings = (\n");
for f in sorted_findings(findings) {
let owasp = if f.owasp.is_empty() { f.cwe.clone() } else { f.owasp.clone() };
data.push_str(&format!(
" (severity: {}, title: {}, agent: {}, cwe: {}, cvss: {}, endpoint: {}, payload: {}, evidence: {}, impact: {}, remediation: {}, votes: {}, confidence: {}),\n",
tq(&f.severity), tq(&f.title), tq(&f.agent), tq(&f.cwe), tq(&f.cvss),
" (severity: {}, title: {}, agent: {}, cwe: {}, owasp: {}, cvss: {}, endpoint: {}, payload: {}, evidence: {}, impact: {}, remediation: {}, votes: {}, confidence: {}),\n",
tq(&f.severity), tq(&f.title), tq(&f.agent), tq(&f.cwe), tq(&owasp), tq(&f.cvss),
tq(&f.endpoint), tq(&f.payload), tq(&f.evidence), tq(&f.impact),
tq(&f.remediation), tq(&f.votes), f.confidence,
));