Skip to main content

cha_core/plugins/
unsafe_api.rs

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