ai_coding_shield/scanner/
mod.rs1mod workflow;
2mod skill;
3mod mcp;
4mod generic;
5pub mod vuln_db; use anyhow::Result;
8use std::path::PathBuf;
9use walkdir::WalkDir;
10
11use crate::types::Artifact;
12
13pub struct Scanner {
14 root_path: PathBuf,
15}
16
17impl Scanner {
18 pub fn new(root_path: PathBuf) -> Self {
19 Self { root_path }
20 }
21
22 pub fn scan(&self) -> Result<Vec<Artifact>> {
23 let mut artifacts = Vec::new();
24
25 for entry in WalkDir::new(&self.root_path)
26 .follow_links(false)
27 .into_iter()
28 .filter_map(|e| e.ok())
29 {
30 let path = entry.path();
31
32 if !path.is_file() {
34 continue;
35 }
36
37 if let Some(artifact) = self.detect_and_parse(path)? {
39 artifacts.push(artifact);
40 }
41 }
42
43 Ok(artifacts)
44 }
45
46 fn detect_and_parse(&self, path: &std::path::Path) -> Result<Option<Artifact>> {
47 let path_str = path.to_string_lossy();
48 let ext = path.extension().map(|e| e.to_string_lossy().to_string()).unwrap_or_default();
49
50 if path_str.contains("workflows") && ext == "md" {
52 return Ok(Some(workflow::parse_workflow(path)?));
53 }
54
55 if path_str.contains("skills") && path.file_name().map_or(false, |n| n == "SKILL.md") {
57 return Ok(Some(skill::parse_skill(path)?));
58 }
59
60 if path_str.contains("rules") && ext == "md" {
62 return Ok(Some(workflow::parse_workflow(path)?));
63 }
64
65 if path_str.to_lowercase().contains("mcp") && ext == "json" {
67 if let Some(artifact) = mcp::McpScanner::scan_file(path)? {
69 return Ok(Some(artifact));
70 }
71 }
72
73 let script_exts = ["sh", "bash", "py", "js", "ts", "rs", "go", "java", "pl", "rb", "php", "txt", "pem", "json"];
76 if script_exts.contains(&ext.as_str()) {
77 if ext == "json" && path_str.contains("mcp") {
79 } else {
81 return Ok(Some(generic::parse_generic_file(path)?));
82 }
83 }
84
85 Ok(None)
86 }
87}