syncable_cli/agent/tools/
security.rs1use rig::completion::ToolDefinition;
4use rig::tool::Tool;
5use serde::{Deserialize, Serialize};
6use serde_json::json;
7use std::path::PathBuf;
8
9use crate::analyzer::security::turbo::{TurboSecurityAnalyzer, TurboConfig, ScanMode};
10
11#[derive(Debug, Deserialize)]
16pub struct SecurityScanArgs {
17 pub mode: Option<String>,
18 pub path: Option<String>,
19}
20
21#[derive(Debug, thiserror::Error)]
22#[error("Security scan error: {0}")]
23pub struct SecurityScanError(String);
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct SecurityScanTool {
27 project_path: PathBuf,
28}
29
30impl SecurityScanTool {
31 pub fn new(project_path: PathBuf) -> Self {
32 Self { project_path }
33 }
34}
35
36impl Tool for SecurityScanTool {
37 const NAME: &'static str = "security_scan";
38
39 type Error = SecurityScanError;
40 type Args = SecurityScanArgs;
41 type Output = String;
42
43 async fn definition(&self, _prompt: String) -> ToolDefinition {
44 ToolDefinition {
45 name: Self::NAME.to_string(),
46 description: "Perform a security scan to detect potential secrets, API keys, passwords, and sensitive data that might be accidentally committed.".to_string(),
47 parameters: json!({
48 "type": "object",
49 "properties": {
50 "mode": {
51 "type": "string",
52 "enum": ["lightning", "fast", "balanced", "thorough", "paranoid"],
53 "description": "Scan mode: lightning (fast), balanced (recommended), thorough, or paranoid"
54 },
55 "path": {
56 "type": "string",
57 "description": "Optional subdirectory path to scan"
58 }
59 }
60 }),
61 }
62 }
63
64 async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
65 let path = match args.path {
66 Some(subpath) => self.project_path.join(subpath),
67 None => self.project_path.clone(),
68 };
69
70 let scan_mode = match args.mode.as_deref() {
71 Some("lightning") => ScanMode::Lightning,
72 Some("fast") => ScanMode::Fast,
73 Some("thorough") => ScanMode::Thorough,
74 Some("paranoid") => ScanMode::Paranoid,
75 _ => ScanMode::Balanced,
76 };
77
78 let config = TurboConfig {
79 scan_mode,
80 ..TurboConfig::default()
81 };
82
83 let scanner = TurboSecurityAnalyzer::new(config)
84 .map_err(|e| SecurityScanError(format!("Failed to create scanner: {}", e)))?;
85
86 let report = scanner.analyze_project(&path)
87 .map_err(|e| SecurityScanError(format!("Scan failed: {}", e)))?;
88
89 let result = json!({
90 "total_findings": report.total_findings,
91 "overall_score": report.overall_score,
92 "risk_level": format!("{:?}", report.risk_level),
93 "files_scanned": report.files_scanned,
94 "findings": report.findings.iter().take(50).map(|f| {
95 json!({
96 "title": f.title,
97 "description": f.description,
98 "severity": format!("{:?}", f.severity),
99 "category": format!("{:?}", f.category),
100 "file_path": f.file_path.as_ref().map(|p| p.display().to_string()),
101 "line_number": f.line_number,
102 "evidence": f.evidence.as_ref().map(|e| e.chars().take(100).collect::<String>()),
103 })
104 }).collect::<Vec<_>>(),
105 "recommendations": report.recommendations.iter().take(10).collect::<Vec<_>>(),
106 "scan_mode": args.mode.as_deref().unwrap_or("balanced"),
107 });
108
109 serde_json::to_string_pretty(&result)
110 .map_err(|e| SecurityScanError(format!("Failed to serialize: {}", e)))
111 }
112}
113
114#[derive(Debug, Deserialize)]
119pub struct VulnerabilitiesArgs {
120 pub path: Option<String>,
121}
122
123#[derive(Debug, thiserror::Error)]
124#[error("Vulnerability check error: {0}")]
125pub struct VulnerabilitiesError(String);
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct VulnerabilitiesTool {
129 project_path: PathBuf,
130}
131
132impl VulnerabilitiesTool {
133 pub fn new(project_path: PathBuf) -> Self {
134 Self { project_path }
135 }
136}
137
138impl Tool for VulnerabilitiesTool {
139 const NAME: &'static str = "check_vulnerabilities";
140
141 type Error = VulnerabilitiesError;
142 type Args = VulnerabilitiesArgs;
143 type Output = String;
144
145 async fn definition(&self, _prompt: String) -> ToolDefinition {
146 ToolDefinition {
147 name: Self::NAME.to_string(),
148 description: "Check the project's dependencies for known security vulnerabilities (CVEs).".to_string(),
149 parameters: json!({
150 "type": "object",
151 "properties": {
152 "path": {
153 "type": "string",
154 "description": "Optional subdirectory path to check"
155 }
156 }
157 }),
158 }
159 }
160
161 async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
162 let path = match args.path {
163 Some(subpath) => self.project_path.join(subpath),
164 None => self.project_path.clone(),
165 };
166
167 let parser = crate::analyzer::dependency_parser::DependencyParser::new();
168 let dependencies = parser
169 .parse_all_dependencies(&path)
170 .map_err(|e| VulnerabilitiesError(format!("Failed to parse dependencies: {}", e)))?;
171
172 if dependencies.is_empty() {
173 return Ok(json!({
174 "message": "No dependencies found in project",
175 "total_vulnerabilities": 0
176 }).to_string());
177 }
178
179 let checker = crate::analyzer::vulnerability::VulnerabilityChecker::new();
180 let report = checker
181 .check_all_dependencies(&dependencies, &path)
182 .await
183 .map_err(|e| VulnerabilitiesError(format!("Vulnerability check failed: {}", e)))?;
184
185 let result = json!({
186 "total_vulnerabilities": report.total_vulnerabilities,
187 "critical_count": report.critical_count,
188 "high_count": report.high_count,
189 "medium_count": report.medium_count,
190 "low_count": report.low_count,
191 "vulnerable_dependencies": report.vulnerable_dependencies.iter().take(20).map(|dep| {
192 json!({
193 "name": dep.name,
194 "version": dep.version,
195 "language": dep.language.as_str(),
196 "vulnerabilities": dep.vulnerabilities.iter().map(|v| {
197 json!({
198 "id": v.id,
199 "title": v.title,
200 "severity": format!("{:?}", v.severity),
201 "cve": v.cve,
202 "patched_versions": v.patched_versions,
203 })
204 }).collect::<Vec<_>>()
205 })
206 }).collect::<Vec<_>>()
207 });
208
209 serde_json::to_string_pretty(&result)
210 .map_err(|e| VulnerabilitiesError(format!("Failed to serialize: {}", e)))
211 }
212}