cha_core/plugins/
unsafe_api.rs1use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
2
3pub struct UnsafeApiAnalyzer;
10
11impl Plugin for UnsafeApiAnalyzer {
12 fn name(&self) -> &str {
13 "unsafe_api"
14 }
15
16 fn smells(&self) -> Vec<String> {
17 vec!["unsafe_api".into()]
18 }
19
20 fn description(&self) -> &str {
21 "Dangerous function calls (eval/exec/system)"
22 }
23
24 fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
25 let lang = &ctx.model.language;
26 let patterns = patterns_for(lang);
27 if patterns.is_empty() {
28 return vec![];
29 }
30 let mut findings = Vec::new();
31 for (i, line) in ctx.file.content.lines().enumerate() {
32 let trimmed = line.trim();
33 if trimmed.starts_with("//") || trimmed.starts_with('#') || trimmed.starts_with("/*") {
34 continue;
35 }
36 for &(pat, msg) in &patterns {
37 if line.contains(pat) && !is_in_string(line, pat) {
38 let col = line.find(pat).unwrap_or(0);
39 findings.push(Finding {
40 smell_name: "unsafe_api".into(),
41 category: SmellCategory::Security,
42 severity: Severity::Warning,
43 location: Location {
44 path: ctx.file.path.clone(),
45 start_line: i + 1,
46 start_col: col,
47 end_line: i + 1,
48 end_col: col + pat.len(),
49 name: None,
50 },
51 message: format!("Potentially dangerous: `{pat}` — {msg}"),
52 suggested_refactorings: vec!["Use a safe alternative".into()],
53 ..Default::default()
54 });
55 break; }
57 }
58 }
59 findings
60 }
61}
62
63fn is_in_string(line: &str, pat: &str) -> bool {
65 if let Some(pos) = line.find(pat) {
66 let before = &line[..pos];
67 let quotes = before.matches('"').count();
68 quotes % 2 == 1 } else {
70 false
71 }
72}
73
74fn patterns_for(lang: &str) -> Vec<(&'static str, &'static str)> {
76 match lang {
77 "rust" => vec![("unsafe ", "unsafe block/fn — review for memory safety")],
78 "python" => vec![
79 ("eval(", "eval() executes arbitrary code"),
80 ("exec(", "exec() executes arbitrary code"),
81 ("os.system(", "os.system() is vulnerable to shell injection"),
82 ("subprocess.call(", "prefer subprocess.run with shell=False"),
83 (
84 "pickle.load",
85 "pickle deserialization can execute arbitrary code",
86 ),
87 ],
88 "typescript" | "javascript" => vec![
89 ("eval(", "eval() executes arbitrary code"),
90 ("innerHTML", "innerHTML can lead to XSS"),
91 (
92 "dangerouslySetInnerHTML",
93 "React escape hatch — review for XSS",
94 ),
95 ("document.write(", "document.write can lead to XSS"),
96 ],
97 "c" | "cpp" => vec![
98 ("gets(", "gets() has no bounds checking — use fgets()"),
99 (
100 "sprintf(",
101 "sprintf() has no bounds checking — use snprintf()",
102 ),
103 ("strcpy(", "strcpy() has no bounds checking — use strncpy()"),
104 ("strcat(", "strcat() has no bounds checking — use strncat()"),
105 ("system(", "system() is vulnerable to shell injection"),
106 ],
107 "go" => vec![
108 ("exec.Command(", "review for command injection"),
109 ("template.HTML(", "bypasses HTML escaping — review for XSS"),
110 ],
111 _ => vec![],
112 }
113}