Add DAST, graph modules, toast notifications, and dashboard enhancements

Add DAST scanning and code knowledge graph features across the stack:
- compliance-dast and compliance-graph workspace crates
- Agent API handlers and routes for DAST targets/scans and graph builds
- Core models and traits for DAST and graph domains
- Dashboard pages for DAST targets/findings/overview and graph explorer/impact
- Toast notification system with auto-dismiss for async action feedback
- Button click animations and disabled states for better UX

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-03-04 13:53:50 +01:00
parent 03ee69834d
commit cea8f59e10
69 changed files with 8745 additions and 54 deletions

View File

@@ -0,0 +1,132 @@
use std::collections::HashMap;
use compliance_core::error::CoreError;
use tracing::info;
/// Result of reconnaissance scanning
#[derive(Debug, Clone)]
pub struct ReconResult {
pub technologies: Vec<String>,
pub interesting_headers: HashMap<String, String>,
pub server: Option<String>,
pub open_ports: Vec<u16>,
}
/// Agent that performs reconnaissance on a target
pub struct ReconAgent {
http: reqwest::Client,
}
impl ReconAgent {
pub fn new(http: reqwest::Client) -> Self {
Self { http }
}
/// Perform reconnaissance on a target URL
pub async fn scan(&self, base_url: &str) -> Result<ReconResult, CoreError> {
let mut result = ReconResult {
technologies: Vec::new(),
interesting_headers: HashMap::new(),
server: None,
open_ports: Vec::new(),
};
// HTTP header fingerprinting
let response = self
.http
.get(base_url)
.send()
.await
.map_err(|e| CoreError::Dast(format!("Failed to connect to target: {e}")))?;
let headers = response.headers();
// Extract server info
if let Some(server) = headers.get("server") {
let server_str = server.to_str().unwrap_or("unknown").to_string();
result.server = Some(server_str.clone());
result.technologies.push(server_str);
}
// Detect technologies from headers
let security_headers = [
"x-powered-by",
"x-aspnet-version",
"x-frame-options",
"x-xss-protection",
"x-content-type-options",
"strict-transport-security",
"content-security-policy",
"x-generator",
];
for header_name in &security_headers {
if let Some(value) = headers.get(*header_name) {
let value_str = value.to_str().unwrap_or("").to_string();
result
.interesting_headers
.insert(header_name.to_string(), value_str.clone());
if *header_name == "x-powered-by" || *header_name == "x-generator" {
result.technologies.push(value_str);
}
}
}
// Check for missing security headers
let missing_security = [
"strict-transport-security",
"x-content-type-options",
"x-frame-options",
];
for header in &missing_security {
if !headers.contains_key(*header) {
result.interesting_headers.insert(
format!("missing:{header}"),
"Not present".to_string(),
);
}
}
// Detect technology from response body
let body = response
.text()
.await
.map_err(|e| CoreError::Dast(format!("Failed to read response: {e}")))?;
self.detect_technologies_from_body(&body, &mut result);
info!(
url = base_url,
technologies = ?result.technologies,
"Reconnaissance complete"
);
Ok(result)
}
fn detect_technologies_from_body(&self, body: &str, result: &mut ReconResult) {
let patterns = [
("React", r#"react"#),
("Angular", r#"ng-version"#),
("Vue.js", r#"vue"#),
("jQuery", r#"jquery"#),
("WordPress", r#"wp-content"#),
("Django", r#"csrfmiddlewaretoken"#),
("Rails", r#"csrf-token"#),
("Laravel", r#"laravel"#),
("Express", r#"express"#),
("Next.js", r#"__NEXT_DATA__"#),
("Nuxt.js", r#"__NUXT__"#),
];
let body_lower = body.to_lowercase();
for (tech, pattern) in &patterns {
if body_lower.contains(&pattern.to_lowercase()) {
if !result.technologies.contains(&tech.to_string()) {
result.technologies.push(tech.to_string());
}
}
}
}
}