From bae24f9cf8df1225d79a4f59f66259f7e7823fe3 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Mon, 30 Mar 2026 07:11:23 +0000 Subject: [PATCH] fix: cascade-delete DAST targets, pentests, and downstream data on repo delete (#50) --- compliance-agent/src/api/handlers/repos.rs | 87 ++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/compliance-agent/src/api/handlers/repos.rs b/compliance-agent/src/api/handlers/repos.rs index 7dfd77b..891bcd5 100644 --- a/compliance-agent/src/api/handlers/repos.rs +++ b/compliance-agent/src/api/handlers/repos.rs @@ -237,5 +237,92 @@ pub async fn delete_repository( .delete_many(doc! { "repo_id": &id }) .await; + // Cascade delete DAST targets linked to this repo, and all their downstream data + // (scan runs, findings, pentest sessions, attack chains, messages) + if let Ok(mut cursor) = db.dast_targets().find(doc! { "repo_id": &id }).await { + use futures_util::StreamExt; + while let Some(Ok(target)) = cursor.next().await { + let target_id = target.id.map(|oid| oid.to_hex()).unwrap_or_default(); + if !target_id.is_empty() { + cascade_delete_dast_target(db, &target_id).await; + } + } + } + + // Also delete pentest sessions linked directly to this repo (not via target) + if let Ok(mut cursor) = db.pentest_sessions().find(doc! { "repo_id": &id }).await { + use futures_util::StreamExt; + while let Some(Ok(session)) = cursor.next().await { + let session_id = session.id.map(|oid| oid.to_hex()).unwrap_or_default(); + if !session_id.is_empty() { + let _ = db + .attack_chain_nodes() + .delete_many(doc! { "session_id": &session_id }) + .await; + let _ = db + .pentest_messages() + .delete_many(doc! { "session_id": &session_id }) + .await; + // Delete DAST findings produced by this session + let _ = db + .dast_findings() + .delete_many(doc! { "session_id": &session_id }) + .await; + } + } + } + let _ = db + .pentest_sessions() + .delete_many(doc! { "repo_id": &id }) + .await; + Ok(Json(serde_json::json!({ "status": "deleted" }))) } + +/// Cascade-delete a DAST target and all its downstream data. +async fn cascade_delete_dast_target(db: &crate::database::Database, target_id: &str) { + // Delete pentest sessions for this target (and their attack chains + messages) + if let Ok(mut cursor) = db + .pentest_sessions() + .find(doc! { "target_id": target_id }) + .await + { + use futures_util::StreamExt; + while let Some(Ok(session)) = cursor.next().await { + let session_id = session.id.map(|oid| oid.to_hex()).unwrap_or_default(); + if !session_id.is_empty() { + let _ = db + .attack_chain_nodes() + .delete_many(doc! { "session_id": &session_id }) + .await; + let _ = db + .pentest_messages() + .delete_many(doc! { "session_id": &session_id }) + .await; + let _ = db + .dast_findings() + .delete_many(doc! { "session_id": &session_id }) + .await; + } + } + } + let _ = db + .pentest_sessions() + .delete_many(doc! { "target_id": target_id }) + .await; + + // Delete DAST scan runs and their findings + let _ = db + .dast_findings() + .delete_many(doc! { "target_id": target_id }) + .await; + let _ = db + .dast_scan_runs() + .delete_many(doc! { "target_id": target_id }) + .await; + + // Delete the target itself + if let Ok(oid) = mongodb::bson::oid::ObjectId::parse_str(target_id) { + let _ = db.dast_targets().delete_one(doc! { "_id": oid }).await; + } +}