securegit 0.8.5

Zero-trust git replacement with 12 built-in security scanners, LLM redteam bridge, universal undo, durable backups, and a 50-tool MCP server
Documentation
use crate::core::{Finding, Severity};
use crate::plugins::traits::{PluginError, PluginReport, ScanContext, ScanPhase, SecurityPlugin};
use async_trait::async_trait;
use std::time::Instant;

pub struct BinaryScanner;

impl Default for BinaryScanner {
    fn default() -> Self {
        Self::new()
    }
}

impl BinaryScanner {
    pub fn new() -> Self {
        Self
    }

    fn is_binary(&self, data: &[u8]) -> bool {
        if data.is_empty() {
            return false;
        }

        // Check for null bytes (strong indicator of binary)
        if data.contains(&0) {
            return true;
        }

        // Check for common binary magic bytes
        if data.len() >= 4 {
            let header = &data[0..4];

            // ELF
            if header == b"\x7fELF" {
                return true;
            }

            // PE/COFF
            if header[0..2] == *b"MZ" {
                return true;
            }

            // Mach-O
            if header == b"\xfe\xed\xfa\xce" || header == b"\xfe\xed\xfa\xcf" {
                return true;
            }
        }

        // High ratio of non-printable characters
        let non_printable = data
            .iter()
            .filter(|&&b| b < 32 && b != b'\n' && b != b'\r' && b != b'\t')
            .count();

        let ratio = non_printable as f64 / data.len() as f64;
        ratio > 0.3
    }

    fn detect_executable_type(&self, data: &[u8]) -> Option<&'static str> {
        if data.len() < 4 {
            return None;
        }

        let header = &data[0..4];

        if header == b"\x7fELF" {
            return Some("ELF");
        }

        if header[0..2] == *b"MZ" {
            return Some("PE/Windows");
        }

        if header == b"\xfe\xed\xfa\xce" || header == b"\xfe\xed\xfa\xcf" {
            return Some("Mach-O/macOS");
        }

        None
    }
}

#[async_trait]
impl SecurityPlugin for BinaryScanner {
    fn name(&self) -> &str {
        "binary"
    }

    fn version(&self) -> &str {
        "0.1.0"
    }

    fn description(&self) -> &str {
        "Detect unexpected binary/executable files"
    }

    fn scan_phase(&self) -> ScanPhase {
        ScanPhase::PostExtract
    }

    async fn initialize(&mut self) -> Result<(), PluginError> {
        Ok(())
    }

    async fn scan(&self, context: &ScanContext<'_>) -> Result<PluginReport, PluginError> {
        let start = Instant::now();
        let mut report = PluginReport::new(self.name().to_string());

        if let Some(content) = context.file_content {
            if self.is_binary(content) {
                let exe_type = self.detect_executable_type(content);

                let (severity, description) = if let Some(ref etype) = exe_type {
                    (
                        Severity::High,
                        format!("Executable binary found: {:?}", etype),
                    )
                } else {
                    (
                        Severity::Medium,
                        "Binary file found in source repository".to_string(),
                    )
                };

                let finding = Finding::new("BIN-001".to_string(), description.clone(), severity)
                    .with_file(context.path.to_path_buf())
                    .with_description(format!(
                        "{}. Binary files in source repositories can be a security risk. \
                     Verify this file is legitimate and expected.",
                        description
                    ));

                report.findings.push(finding);
            }

            report.scanned_files = 1;
        }

        report.duration_ms = start.elapsed().as_millis() as u64;
        Ok(report)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::plugins::traits::ScanContext;

    #[tokio::test]
    async fn test_elf_detection() {
        let scanner = BinaryScanner::new();
        let content = b"\x7fELF\x00\x00\x00\x00";
        let context = ScanContext {
            path: std::path::Path::new("test.bin"),
            scan_phase: ScanPhase::PostExtract,
            file_content: Some(content),
            metadata: std::collections::HashMap::new(),
        };
        let report = scanner.scan(&context).await.unwrap();
        assert!(!report.findings.is_empty());
        assert!(report.findings[0].severity >= Severity::High);
    }

    #[tokio::test]
    async fn test_pe_detection() {
        let scanner = BinaryScanner::new();
        let content = b"MZ\x00\x00\x00\x00\x00\x00";
        let context = ScanContext {
            path: std::path::Path::new("test.exe"),
            scan_phase: ScanPhase::PostExtract,
            file_content: Some(content),
            metadata: std::collections::HashMap::new(),
        };
        let report = scanner.scan(&context).await.unwrap();
        assert!(!report.findings.is_empty());
    }

    #[tokio::test]
    async fn test_text_file_passes() {
        let scanner = BinaryScanner::new();
        let content = b"hello world\n";
        let context = ScanContext {
            path: std::path::Path::new("test.txt"),
            scan_phase: ScanPhase::PostExtract,
            file_content: Some(content),
            metadata: std::collections::HashMap::new(),
        };
        let report = scanner.scan(&context).await.unwrap();
        assert_eq!(report.findings.len(), 0);
    }
}