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 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; // one finding per line
56                }
57            }
58        }
59        findings
60    }
61}
62
63/// Heuristic: pattern is likely inside a string literal if preceded by a quote.
64fn 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 // odd number of quotes = inside string
69    } else {
70        false
71    }
72}
73
74// cha:ignore unsafe_api
75fn 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}