|
|
|
|
|
|
| use regex::Regex;
|
| use std::sync::OnceLock;
|
|
|
|
|
| fn js_eval_regex() -> &'static Regex {
|
| static REGEX: OnceLock<Regex> = OnceLock::new();
|
| REGEX.get_or_init(|| Regex::new(r#"\beval\s*\("#).expect("Valid regex"))
|
| }
|
|
|
|
|
| fn js_exec_regex() -> &'static Regex {
|
| static REGEX: OnceLock<Regex> = OnceLock::new();
|
| REGEX.get_or_init(|| Regex::new(r#"\bexec\s*\("#).expect("Valid regex"))
|
| }
|
|
|
|
|
| fn js_function_regex() -> &'static Regex {
|
| static REGEX: OnceLock<Regex> = OnceLock::new();
|
| REGEX.get_or_init(|| Regex::new(r"new\s+Function\s*\(").expect("Valid regex"))
|
| }
|
|
|
|
|
| fn js_timeout_regex() -> &'static Regex {
|
| static REGEX: OnceLock<Regex> = OnceLock::new();
|
| REGEX.get_or_init(|| {
|
| Regex::new(r#"(?:set|clear)(?:Timeout|Interval)\s*\(\s*['"]"#).expect("Valid regex")
|
| })
|
| }
|
|
|
|
|
| fn python_exec_regex() -> &'static Regex {
|
| static REGEX: OnceLock<Regex> = OnceLock::new();
|
| REGEX.get_or_init(|| Regex::new(r#"(?:^|\s|=)(?:exec|eval)\s*\("#).expect("Valid regex"))
|
| }
|
|
|
|
|
| fn python_subprocess_regex() -> &'static Regex {
|
| static REGEX: OnceLock<Regex> = OnceLock::new();
|
| REGEX.get_or_init(|| Regex::new(r"subprocess\.(?:call|run|Popen)\s*\(").expect("Valid regex"))
|
| }
|
|
|
|
|
| fn java_runtime_regex() -> &'static Regex {
|
| static REGEX: OnceLock<Regex> = OnceLock::new();
|
| REGEX.get_or_init(|| Regex::new(r"(?:Runtime|getRuntime).*\.exec\s*\(").expect("Valid regex"))
|
| }
|
|
|
|
|
| fn shell_eval_regex() -> &'static Regex {
|
| static REGEX: OnceLock<Regex> = OnceLock::new();
|
| REGEX.get_or_init(|| Regex::new(r"(?:^|\s)eval\s+").expect("Valid regex"))
|
| }
|
|
|
|
|
| fn indirect_call_regex() -> &'static Regex {
|
| static REGEX: OnceLock<Regex> = OnceLock::new();
|
| REGEX.get_or_init(|| {
|
| Regex::new(r#"(?:\$\(|`|(?:call|invoke|apply)\s*\()"#).expect("Valid regex")
|
| })
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| pub fn check_eval(artifact: &str) -> (bool, Vec<String>) {
|
| let mut findings = Vec::new();
|
|
|
| for (line_num, line) in artifact.lines().enumerate() {
|
| let line_no = line_num + 1;
|
|
|
|
|
| let trimmed = line.trim_start();
|
| if trimmed.starts_with("//") || trimmed.starts_with("#") {
|
| continue;
|
| }
|
|
|
|
|
| if js_eval_regex().is_match(line) {
|
| findings.push(format!("Line {}: JavaScript eval() detected", line_no));
|
| }
|
|
|
|
|
| if js_exec_regex().is_match(line) {
|
| findings.push(format!("Line {}: JavaScript exec() detected", line_no));
|
| }
|
|
|
|
|
| if js_function_regex().is_match(line) {
|
| findings.push(format!("Line {}: Function constructor with dynamic code detected", line_no));
|
| }
|
|
|
|
|
| if js_timeout_regex().is_match(line) {
|
| findings.push(format!("Line {}: setTimeout/setInterval with string detected", line_no));
|
| }
|
|
|
|
|
| if python_exec_regex().is_match(line) {
|
| findings.push(format!("Line {}: Python exec() or eval() detected", line_no));
|
| }
|
|
|
|
|
| if python_subprocess_regex().is_match(line) {
|
| findings.push(format!("Line {}: Python subprocess execution detected", line_no));
|
| }
|
|
|
|
|
| if java_runtime_regex().is_match(line) {
|
| findings.push(format!("Line {}: Java Runtime.exec() detected", line_no));
|
| }
|
|
|
|
|
| if shell_eval_regex().is_match(line) {
|
| findings.push(format!("Line {}: Shell eval command detected", line_no));
|
| }
|
|
|
|
|
| if indirect_call_regex().is_match(line) {
|
| findings.push(format!("Line {}: Indirect function call or command substitution detected", line_no));
|
| }
|
| }
|
|
|
| let passed = findings.is_empty();
|
| (passed, findings)
|
| }
|
|
|
| #[cfg(test)]
|
| mod tests {
|
| use super::*;
|
|
|
| #[test]
|
| fn test_detect_eval() {
|
| let artifact = "const result = eval('1 + 2')";
|
| let (passed, findings) = check_eval(artifact);
|
| assert!(!passed);
|
| assert!(!findings.is_empty());
|
| assert!(findings[0].contains("eval()"));
|
| }
|
|
|
| #[test]
|
| fn test_detect_function_constructor() {
|
| let artifact = "const fn = new Function('a', 'b', 'return a + b');";
|
| let (passed, findings) = check_eval(artifact);
|
| assert!(!passed);
|
| assert!(!findings.is_empty());
|
| assert!(findings[0].contains("Function constructor"));
|
| }
|
|
|
| #[test]
|
| fn test_eval_in_comment_allowed() {
|
| let artifact = "// eval() is dangerous\nfunction safe() { return 42; }";
|
| let (passed, _findings) = check_eval(artifact);
|
| assert!(passed);
|
| }
|
|
|
| #[test]
|
| fn test_clean_code() {
|
| let artifact = "function add(a, b) { return a + b; }";
|
| let (passed, findings) = check_eval(artifact);
|
| assert!(passed);
|
| assert!(findings.is_empty());
|
| }
|
|
|
| #[test]
|
| fn test_detect_settimeout_with_string() {
|
| let artifact = "setTimeout('doSomething()', 1000);";
|
| let (passed, findings) = check_eval(artifact);
|
| assert!(!passed);
|
| assert!(!findings.is_empty());
|
| assert!(findings[0].contains("setTimeout"));
|
| }
|
|
|
| #[test]
|
| fn test_detect_python_exec() {
|
| let artifact = "exec(code_string)";
|
| let (passed, findings) = check_eval(artifact);
|
| assert!(!passed);
|
| assert!(!findings.is_empty());
|
| assert!(findings[0].contains("Python exec()") || findings[0].contains("exec()") || findings[0].contains("detected"));
|
| }
|
|
|
| #[test]
|
| fn test_detect_subprocess_call() {
|
| let artifact = "subprocess.call(user_input)";
|
| let (passed, findings) = check_eval(artifact);
|
| assert!(!passed);
|
| assert!(!findings.is_empty());
|
| assert!(findings[0].contains("subprocess"));
|
| }
|
|
|
| #[test]
|
| fn test_detect_java_runtime() {
|
| let artifact = "Runtime.getRuntime().exec(command);";
|
| let (passed, findings) = check_eval(artifact);
|
| assert!(!passed);
|
| assert!(!findings.is_empty());
|
| assert!(findings[0].contains("Runtime.exec()") || findings[0].contains("exec()") || findings[0].contains("detected"));
|
| }
|
|
|
| #[test]
|
| fn test_line_numbers_in_eval_findings() {
|
| let artifact = "line 1\nline 2\neval('dangerous')\nline 4";
|
| let (passed, findings) = check_eval(artifact);
|
| assert!(!passed);
|
| assert!(findings[0].contains("Line 3"));
|
| }
|
| }
|
|
|