All checks were successful
CI / Clippy (push) Successful in 4m15s
CI / Security Audit (push) Has been skipped
CI / Tests (push) Has been skipped
CI / Clippy (pull_request) Successful in 4m16s
CI / Security Audit (pull_request) Has been skipped
CI / Tests (pull_request) Has been skipped
CI / Detect Changes (push) Has been skipped
CI / Detect Changes (pull_request) 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 / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
CI / Format (push) Successful in 27s
CI / Format (pull_request) Successful in 3s
Fix dead code warnings, redundant clones, boolean simplification, format-in-format-args, type complexity, and Box::new of Default across compliance-dast, compliance-agent, and compliance-dashboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
129 lines
4.6 KiB
Rust
129 lines
4.6 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use compliance_core::error::CoreError;
|
|
use compliance_core::traits::pentest_tool::{PentestTool, PentestToolContext, PentestToolResult};
|
|
use serde_json::json;
|
|
use tracing::info;
|
|
|
|
use crate::recon::ReconAgent;
|
|
|
|
/// PentestTool wrapper around the existing ReconAgent.
|
|
///
|
|
/// Performs HTTP header fingerprinting and technology detection.
|
|
/// Returns structured recon data for the LLM to use when planning attacks.
|
|
pub struct ReconTool {
|
|
http: reqwest::Client,
|
|
agent: ReconAgent,
|
|
}
|
|
|
|
impl ReconTool {
|
|
pub fn new(http: reqwest::Client) -> Self {
|
|
let agent = ReconAgent::new(http.clone());
|
|
Self { http, agent }
|
|
}
|
|
}
|
|
|
|
impl PentestTool for ReconTool {
|
|
fn name(&self) -> &str {
|
|
"recon"
|
|
}
|
|
|
|
fn description(&self) -> &str {
|
|
"Performs reconnaissance on a target URL. Fingerprints HTTP headers, detects server \
|
|
technologies and frameworks. Returns structured data about the target's technology stack."
|
|
}
|
|
|
|
fn input_schema(&self) -> serde_json::Value {
|
|
json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"url": {
|
|
"type": "string",
|
|
"description": "Base URL to perform reconnaissance on"
|
|
},
|
|
"additional_paths": {
|
|
"type": "array",
|
|
"description": "Optional additional paths to probe for technology fingerprinting",
|
|
"items": { "type": "string" }
|
|
}
|
|
},
|
|
"required": ["url"]
|
|
})
|
|
}
|
|
|
|
fn execute<'a>(
|
|
&'a self,
|
|
input: serde_json::Value,
|
|
_context: &'a PentestToolContext,
|
|
) -> std::pin::Pin<
|
|
Box<dyn std::future::Future<Output = Result<PentestToolResult, CoreError>> + Send + 'a>,
|
|
> {
|
|
Box::pin(async move {
|
|
let url = input
|
|
.get("url")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or_else(|| CoreError::Dast("Missing required 'url' parameter".to_string()))?;
|
|
|
|
let additional_paths: Vec<String> = input
|
|
.get("additional_paths")
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| {
|
|
arr.iter()
|
|
.filter_map(|v| v.as_str().map(String::from))
|
|
.collect()
|
|
})
|
|
.unwrap_or_default();
|
|
|
|
let result = self.agent.scan(url).await?;
|
|
|
|
// Scan additional paths for more technology signals
|
|
let mut extra_technologies: Vec<String> = Vec::new();
|
|
let mut extra_headers: HashMap<String, String> = HashMap::new();
|
|
|
|
let base_url = url.trim_end_matches('/');
|
|
for path in &additional_paths {
|
|
let probe_url = format!("{base_url}/{}", path.trim_start_matches('/'));
|
|
if let Ok(resp) = self.http.get(&probe_url).send().await {
|
|
for (key, value) in resp.headers() {
|
|
let k = key.to_string().to_lowercase();
|
|
let v = value.to_str().unwrap_or("").to_string();
|
|
|
|
// Look for technology indicators
|
|
if (k == "x-powered-by" || k == "server" || k == "x-generator")
|
|
&& !result.technologies.contains(&v)
|
|
&& !extra_technologies.contains(&v)
|
|
{
|
|
extra_technologies.push(v.clone());
|
|
}
|
|
extra_headers.insert(format!("{probe_url} -> {k}"), v);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut all_technologies = result.technologies.clone();
|
|
all_technologies.extend(extra_technologies);
|
|
all_technologies.dedup();
|
|
|
|
let tech_count = all_technologies.len();
|
|
info!(url, technologies = tech_count, "Recon complete");
|
|
|
|
Ok(PentestToolResult {
|
|
summary: format!(
|
|
"Recon complete for {url}. Detected {} technologies. Server: {}.",
|
|
tech_count,
|
|
result.server.as_deref().unwrap_or("unknown")
|
|
),
|
|
findings: Vec::new(), // Recon produces data, not findings
|
|
data: json!({
|
|
"base_url": url,
|
|
"server": result.server,
|
|
"technologies": all_technologies,
|
|
"interesting_headers": result.interesting_headers,
|
|
"extra_headers": extra_headers,
|
|
"open_ports": result.open_ports,
|
|
}),
|
|
})
|
|
})
|
|
}
|
|
}
|