ggen_cli_lib/cmds/audit/
security.rs1use clap::{Args, Subcommand};
21use ggen_utils::error::Result;
22#[derive(Args, Debug)]
25pub struct SecurityArgs {
26 #[command(subcommand)]
27 pub action: SecurityAction,
28}
29
30#[derive(Subcommand, Debug)]
31pub enum SecurityAction {
32 Scan(ScanArgs),
34
35 Dependencies(DependenciesArgs),
37
38 Config(ConfigArgs),
40}
41
42#[derive(Args, Debug)]
43pub struct ScanArgs {
44 #[arg(long, default_value = ".")]
46 pub path: String,
47
48 #[arg(long)]
50 pub json: bool,
51
52 #[arg(long)]
54 pub verbose: bool,
55
56 #[arg(long)]
58 pub fix: bool,
59}
60
61#[derive(Args, Debug)]
62pub struct DependenciesArgs {
63 #[arg(long)]
65 pub direct_only: bool,
66
67 #[arg(long)]
69 pub json: bool,
70
71 #[arg(long)]
73 pub update: bool,
74}
75
76#[derive(Args, Debug)]
77pub struct ConfigArgs {
78 #[arg(long)]
80 pub file: Option<String>,
81
82 #[arg(long)]
84 pub json: bool,
85
86 #[arg(long)]
88 pub fix: bool,
89}
90
91pub async fn run(args: &SecurityArgs) -> Result<()> {
92 match &args.action {
93 SecurityAction::Scan(scan_args) => scan_security(scan_args).await,
94 SecurityAction::Dependencies(deps_args) => check_dependencies(deps_args).await,
95 SecurityAction::Config(config_args) => audit_config(config_args).await,
96 }
97}
98
99fn validate_path(path: &str) -> Result<()> {
101 if path.trim().is_empty() {
103 return Err(ggen_utils::error::Error::new("Path cannot be empty"));
104 }
105
106 if path.len() > 1000 {
108 return Err(ggen_utils::error::Error::new(
109 "Path too long (max 1000 characters)",
110 ));
111 }
112
113 if path.contains("..") {
115 return Err(ggen_utils::error::Error::new(
116 "Path traversal detected: path cannot contain '..'",
117 ));
118 }
119
120 Ok(())
121}
122
123fn validate_file_path(file: &Option<String>) -> Result<()> {
125 if let Some(file) = file {
126 if file.trim().is_empty() {
128 return Err(ggen_utils::error::Error::new("File path cannot be empty"));
129 }
130
131 if file.len() > 1000 {
133 return Err(ggen_utils::error::Error::new(
134 "File path too long (max 1000 characters)",
135 ));
136 }
137
138 if file.contains("..") {
140 return Err(ggen_utils::error::Error::new(
141 "Path traversal detected: file path cannot contain '..'",
142 ));
143 }
144
145 if !file.chars().all(|c| {
147 c.is_alphanumeric() || c == '.' || c == '/' || c == '-' || c == '_' || c == '\\'
148 }) {
149 return Err(ggen_utils::error::Error::new(
150 "Invalid file path format: only alphanumeric characters, dots, slashes, dashes, underscores, and backslashes allowed",
151 ));
152 }
153 }
154
155 Ok(())
156}
157
158async fn scan_security(args: &ScanArgs) -> Result<()> {
159 validate_path(&args.path)?;
161
162 println!("🔒 Scanning for security vulnerabilities");
163
164 let mut cmd = std::process::Command::new("cargo");
165 cmd.args(["make", "audit"]);
166
167 if args.json {
168 cmd.arg("--json");
169 }
170
171 if args.verbose {
172 cmd.arg("--verbose");
173 }
174
175 if args.fix {
176 cmd.arg("--fix");
177 }
178
179 cmd.arg("--path").arg(&args.path);
180
181 let output = cmd.output()?;
182
183 if !output.status.success() {
184 let stderr = String::from_utf8_lossy(&output.stderr);
185 return Err(ggen_utils::error::Error::new(&format!(
186 "Security scan failed: {}",
187 stderr
188 )));
189 }
190
191 let stdout = String::from_utf8_lossy(&output.stdout);
192 println!("{}", stdout);
193 Ok(())
194}
195
196async fn check_dependencies(args: &DependenciesArgs) -> Result<()> {
197 println!("📦 Checking dependencies for security vulnerabilities");
198
199 let mut cmd = std::process::Command::new("cargo");
200 cmd.args(["make", "audit-deps"]);
201
202 if args.direct_only {
203 cmd.arg("--direct-only");
204 }
205
206 if args.json {
207 cmd.arg("--json");
208 }
209
210 if args.update {
211 cmd.arg("--update");
212 }
213
214 let output = cmd.output()?;
215
216 if !output.status.success() {
217 let stderr = String::from_utf8_lossy(&output.stderr);
218 return Err(ggen_utils::error::Error::new(&format!(
219 "Dependency security check failed: {}",
220 stderr
221 )));
222 }
223
224 let stdout = String::from_utf8_lossy(&output.stdout);
225 println!("{}", stdout);
226 Ok(())
227}
228
229async fn audit_config(args: &ConfigArgs) -> Result<()> {
230 validate_file_path(&args.file)?;
232
233 println!("⚙️ Auditing configuration files for security issues");
234
235 let mut cmd = std::process::Command::new("cargo");
236 cmd.args(["make", "audit-config"]);
237
238 if let Some(file) = &args.file {
239 cmd.arg("--file").arg(file);
240 }
241
242 if args.json {
243 cmd.arg("--json");
244 }
245
246 if args.fix {
247 cmd.arg("--fix");
248 }
249
250 let output = cmd.output()?;
251
252 if !output.status.success() {
253 let stderr = String::from_utf8_lossy(&output.stderr);
254 return Err(ggen_utils::error::Error::new(&format!(
255 "Configuration audit failed: {}",
256 stderr
257 )));
258 }
259
260 let stdout = String::from_utf8_lossy(&output.stdout);
261 println!("{}", stdout);
262 Ok(())
263}