pub mod api_fuzzer; pub mod auth_bypass; pub mod browser; pub mod console_log_detector; pub mod cookie_analyzer; pub mod cors_checker; pub mod csp_analyzer; pub mod dmarc_checker; pub mod dns_checker; pub mod openapi_parser; pub mod rate_limit_tester; pub mod recon; pub mod security_headers; pub mod sql_injection; pub mod ssrf; pub mod tls_analyzer; pub mod xss; use std::collections::HashMap; use compliance_core::traits::pentest_tool::PentestTool; /// A definition describing a tool for LLM tool_use registration. #[derive(Debug, Clone)] pub struct ToolDefinition { pub name: String, pub description: String, pub input_schema: serde_json::Value, } /// Registry that holds all available pentest tools and provides /// look-up by name. pub struct ToolRegistry { tools: HashMap>, } impl Default for ToolRegistry { fn default() -> Self { Self::new() } } impl ToolRegistry { /// Create a new registry with all built-in tools pre-registered. #[allow(clippy::expect_used)] pub fn new() -> Self { let http = reqwest::Client::builder() .danger_accept_invalid_certs(true) .timeout(std::time::Duration::from_secs(30)) .redirect(reqwest::redirect::Policy::limited(5)) .build() .expect("failed to build HTTP client"); let mut tools: HashMap> = HashMap::new(); // Agent-wrapping tools let register = |tools: &mut HashMap>, tool: Box| { tools.insert(tool.name().to_string(), tool); }; register( &mut tools, Box::new(sql_injection::SqlInjectionTool::new(http.clone())), ); register(&mut tools, Box::new(xss::XssTool::new(http.clone()))); register( &mut tools, Box::new(auth_bypass::AuthBypassTool::new(http.clone())), ); register(&mut tools, Box::new(ssrf::SsrfTool::new(http.clone()))); register( &mut tools, Box::new(api_fuzzer::ApiFuzzerTool::new(http.clone())), ); // New infrastructure / analysis tools register(&mut tools, Box::::default()); register( &mut tools, Box::::default(), ); register( &mut tools, Box::new(tls_analyzer::TlsAnalyzerTool::new(http.clone())), ); register( &mut tools, Box::new(security_headers::SecurityHeadersTool::new(http.clone())), ); register( &mut tools, Box::new(cookie_analyzer::CookieAnalyzerTool::new(http.clone())), ); register( &mut tools, Box::new(csp_analyzer::CspAnalyzerTool::new(http.clone())), ); register( &mut tools, Box::new(rate_limit_tester::RateLimitTesterTool::new(http.clone())), ); register( &mut tools, Box::new(console_log_detector::ConsoleLogDetectorTool::new( http.clone(), )), ); register( &mut tools, Box::new(cors_checker::CorsCheckerTool::new(http.clone())), ); register( &mut tools, Box::new(openapi_parser::OpenApiParserTool::new(http.clone())), ); register(&mut tools, Box::new(recon::ReconTool::new(http))); register(&mut tools, Box::::default()); Self { tools } } /// Look up a tool by name. pub fn get(&self, name: &str) -> Option<&dyn PentestTool> { self.tools.get(name).map(|b| b.as_ref()) } /// Return definitions for every registered tool. pub fn all_definitions(&self) -> Vec { self.tools .values() .map(|t| ToolDefinition { name: t.name().to_string(), description: t.description().to_string(), input_schema: t.input_schema(), }) .collect() } /// Return the names of all registered tools. pub fn list_names(&self) -> Vec { self.tools.keys().cloned().collect() } } #[cfg(test)] mod tests { use super::*; #[test] fn registry_has_all_expected_tools() { let registry = ToolRegistry::new(); let names = registry.list_names(); let expected = [ "recon", "openapi_parser", "dns_checker", "dmarc_checker", "tls_analyzer", "security_headers", "cookie_analyzer", "csp_analyzer", "cors_checker", "rate_limit_tester", "console_log_detector", "sql_injection_scanner", "xss_scanner", "ssrf_scanner", "auth_bypass_scanner", "api_fuzzer", "browser", ]; for name in &expected { assert!( names.contains(&name.to_string()), "Missing tool: {name}. Registered: {names:?}" ); } } #[test] fn registry_get_returns_tool() { let registry = ToolRegistry::new(); assert!(registry.get("recon").is_some()); assert!(registry.get("browser").is_some()); assert!(registry.get("nonexistent").is_none()); } #[test] fn all_definitions_have_valid_schemas() { let registry = ToolRegistry::new(); let defs = registry.all_definitions(); assert!(!defs.is_empty()); for def in &defs { assert!(!def.name.is_empty(), "Tool has empty name"); assert!( !def.description.is_empty(), "Tool {} has empty description", def.name ); assert!( def.input_schema.is_object(), "Tool {} schema is not an object", def.name ); // Every schema should have "type": "object" assert_eq!( def.input_schema.get("type").and_then(|v| v.as_str()), Some("object"), "Tool {} schema type is not 'object'", def.name ); } } #[test] fn browser_tool_schema_has_action_enum() { let registry = ToolRegistry::new(); let browser = registry.get("browser"); assert!(browser.is_some()); let schema = browser.map(|t| t.input_schema()).unwrap_or_default(); let action_prop = schema.get("properties").and_then(|p| p.get("action")); assert!( action_prop.is_some(), "Browser tool missing 'action' property" ); let action_enum = action_prop .and_then(|a| a.get("enum")) .and_then(|e| e.as_array()); assert!(action_enum.is_some(), "Browser action missing enum"); let actions: Vec<&str> = action_enum .into_iter() .flatten() .filter_map(|v| v.as_str()) .collect(); assert!(actions.contains(&"navigate")); assert!(actions.contains(&"screenshot")); assert!(actions.contains(&"click")); assert!(actions.contains(&"fill")); assert!(actions.contains(&"get_content")); assert!(actions.contains(&"close")); } }