Files
compliance-scanner-agent/compliance-dast/src/tools/recon.rs
Sharang Parnerkar ab17a7376a
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: resolve all clippy warnings with RUSTFLAGS="-D warnings"
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>
2026-03-13 08:46:21 +01:00

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,
}),
})
})
}
}