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:
132
compliance-dast/src/recon/mod.rs
Normal file
132
compliance-dast/src/recon/mod.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user