fix: attack chain node linking and disable input while pentest runs
Some checks failed
CI / Security Audit (push) Has been skipped
CI / Tests (push) Has been skipped
CI / Detect Changes (push) Has been skipped
CI / Deploy Agent (push) Has been skipped
CI / Deploy Dashboard (push) Has been skipped
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped
CI / Format (push) Failing after 4s
CI / Clippy (push) Failing after 1m42s

Link attack chain nodes to previous iteration's nodes via parent_node_ids
so the DAG graph shows proper hierarchy instead of flat dots. Disable the
chat input while a pentest session is running since messages have no effect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-03-11 22:12:14 +01:00
parent 0428cba2b8
commit cc6ae7717c
2 changed files with 36 additions and 20 deletions

View File

@@ -172,6 +172,7 @@ impl PentestOrchestrator {
let mut total_findings = 0u32; let mut total_findings = 0u32;
let mut total_tool_calls = 0u32; let mut total_tool_calls = 0u32;
let mut total_successes = 0u32; let mut total_successes = 0u32;
let mut prev_node_ids: Vec<String> = Vec::new();
for _iteration in 0..max_iterations { for _iteration in 0..max_iterations {
let response = self let response = self
@@ -233,6 +234,8 @@ impl PentestOrchestrator {
tool_call_id: None, tool_call_id: None,
}); });
let mut current_batch_node_ids: Vec<String> = Vec::new();
for tc in &tool_calls { for tc in &tool_calls {
total_tool_calls += 1; total_tool_calls += 1;
let node_id = uuid::Uuid::new_v4().to_string(); let node_id = uuid::Uuid::new_v4().to_string();
@@ -244,9 +247,12 @@ impl PentestOrchestrator {
tc.arguments.clone(), tc.arguments.clone(),
String::new(), String::new(),
); );
// Link to previous iteration's nodes
node.parent_node_ids = prev_node_ids.clone();
node.status = AttackNodeStatus::Running; node.status = AttackNodeStatus::Running;
node.started_at = Some(chrono::Utc::now()); node.started_at = Some(chrono::Utc::now());
let _ = self.db.attack_chain_nodes().insert_one(&node).await; let _ = self.db.attack_chain_nodes().insert_one(&node).await;
current_batch_node_ids.push(node_id.clone());
let _ = self.event_tx.send(PentestEvent::ToolStart { let _ = self.event_tx.send(PentestEvent::ToolStart {
node_id: node_id.clone(), node_id: node_id.clone(),
@@ -337,6 +343,9 @@ impl PentestOrchestrator {
}); });
} }
// Advance parent links so next iteration's nodes connect to this batch
prev_node_ids = current_batch_node_ids;
if let Some(sid) = session.id { if let Some(sid) = session.id {
let _ = self let _ = self
.db .db

View File

@@ -560,7 +560,13 @@ pub fn PentestSessionPage(session_id: String) -> Element {
} }
} }
// Input area // Input area — disabled while pentest is running (messages have no effect)
if is_running {
div { style: "flex-shrink: 0; padding: 12px; border-top: 1px solid var(--border-color); text-align: center; color: var(--text-secondary); font-size: 0.85rem;",
Icon { icon: BsPlayCircle, width: 14, height: 14 }
" Pentest is running — input disabled until complete"
}
} else {
div { style: "flex-shrink: 0; padding: 12px; border-top: 1px solid var(--border-color); display: flex; gap: 8px;", div { style: "flex-shrink: 0; padding: 12px; border-top: 1px solid var(--border-color); display: flex; gap: 8px;",
textarea { textarea {
class: "chat-input", class: "chat-input",
@@ -584,6 +590,7 @@ pub fn PentestSessionPage(session_id: String) -> Element {
} }
} }
} }
}
// Right: Findings / Attack Chain tabs // Right: Findings / Attack Chain tabs
div { class: "card", style: "display: flex; flex-direction: column; overflow: hidden;", div { class: "card", style: "display: flex; flex-direction: column; overflow: hidden;",