feat: pentest onboarding — streaming, browser automation, reports, user cleanup (#16)
All checks were successful
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 7s
CI / Deploy Agent (push) Successful in 2s
CI / Deploy Dashboard (push) Successful in 2s
CI / Deploy Docs (push) Successful in 2s
CI / Deploy MCP (push) Successful in 2s

Complete pentest feature overhaul: SSE streaming, session-persistent browser tool (CDP), AES-256 credential encryption, auto-screenshots in reports, code-level remediation correlation, SAST triage chunking, context window optimization, test user cleanup (Keycloak/Auth0/Okta), wizard dropdowns, attack chain improvements, architecture docs with Mermaid diagrams.

Co-authored-by: Sharang Parnerkar <parnerkarsharang@gmail.com>
Reviewed-on: #16
This commit was merged in pull request #16.
This commit is contained in:
2026-03-17 20:32:20 +00:00
parent 11e1c5f438
commit c461faa2fb
57 changed files with 8844 additions and 2423 deletions

View File

@@ -118,9 +118,12 @@ pub(crate) fn cat_label(cat: &str) -> &'static str {
}
}
/// Phase name heuristic based on depth
pub(crate) fn phase_name(depth: usize) -> &'static str {
match depth {
/// Maximum number of display phases — deeper iterations are merged into the last.
const MAX_PHASES: usize = 8;
/// Phase name heuristic based on phase index (not raw BFS depth)
pub(crate) fn phase_name(phase_idx: usize) -> &'static str {
match phase_idx {
0 => "Reconnaissance",
1 => "Analysis",
2 => "Boundary Testing",
@@ -133,8 +136,8 @@ pub(crate) fn phase_name(depth: usize) -> &'static str {
}
/// Short label for phase rail
pub(crate) fn phase_short_name(depth: usize) -> &'static str {
match depth {
pub(crate) fn phase_short_name(phase_idx: usize) -> &'static str {
match phase_idx {
0 => "Recon",
1 => "Analysis",
2 => "Boundary",
@@ -214,7 +217,14 @@ pub(crate) fn compute_phases(steps: &[serde_json::Value]) -> Vec<Vec<usize>> {
}
}
// Group by depth
// Cap depths at MAX_PHASES - 1 so deeper iterations merge into the last phase
for d in depths.iter_mut() {
if *d >= MAX_PHASES {
*d = MAX_PHASES - 1;
}
}
// Group by (capped) depth
let max_depth = depths.iter().copied().max().unwrap_or(0);
let mut phases: Vec<Vec<usize>> = Vec::new();
for d in 0..=max_depth {