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_tool_calls = 0u32;
let mut total_successes = 0u32;
let mut prev_node_ids: Vec<String> = Vec::new();
for _iteration in 0..max_iterations {
let response = self
@@ -233,6 +234,8 @@ impl PentestOrchestrator {
tool_call_id: None,
});
let mut current_batch_node_ids: Vec<String> = Vec::new();
for tc in &tool_calls {
total_tool_calls += 1;
let node_id = uuid::Uuid::new_v4().to_string();
@@ -244,9 +247,12 @@ impl PentestOrchestrator {
tc.arguments.clone(),
String::new(),
);
// Link to previous iteration's nodes
node.parent_node_ids = prev_node_ids.clone();
node.status = AttackNodeStatus::Running;
node.started_at = Some(chrono::Utc::now());
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 {
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 {
let _ = self
.db

View File

@@ -560,27 +560,34 @@ pub fn PentestSessionPage(session_id: String) -> Element {
}
}
// Input area
div { style: "flex-shrink: 0; padding: 12px; border-top: 1px solid var(--border-color); display: flex; gap: 8px;",
textarea {
class: "chat-input",
style: "flex: 1;",
placeholder: "Guide the pentest agent...",
value: "{input_text}",
oninput: move |e| input_text.set(e.value()),
onkeydown: move |e: Event<KeyboardData>| {
if e.key() == Key::Enter && !e.modifiers().shift() {
e.prevent_default();
do_send();
}
},
// 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"
}
button {
class: "btn btn-primary",
style: "align-self: flex-end;",
disabled: *sending.read(),
onclick: move |_| do_send_click(),
"Send"
} else {
div { style: "flex-shrink: 0; padding: 12px; border-top: 1px solid var(--border-color); display: flex; gap: 8px;",
textarea {
class: "chat-input",
style: "flex: 1;",
placeholder: "Guide the pentest agent...",
value: "{input_text}",
oninput: move |e| input_text.set(e.value()),
onkeydown: move |e: Event<KeyboardData>| {
if e.key() == Key::Enter && !e.modifiers().shift() {
e.prevent_default();
do_send();
}
},
}
button {
class: "btn btn-primary",
style: "align-self: flex-end;",
disabled: *sending.read(),
onclick: move |_| do_send_click(),
"Send"
}
}
}
}