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 super::{CliError, CliToolBridge};
use crate::core::{Confidence, Finding, Severity};
use std::path::Path;

const BINARY: &str = "picklescan";

pub struct PickleScanBridge {
    bridge: CliToolBridge,
}

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

impl PickleScanBridge {
    pub fn new() -> Self {
        Self {
            bridge: CliToolBridge::new(BINARY),
        }
    }

    pub fn is_available(&self) -> bool {
        self.bridge.is_available()
    }

    /// Scan a directory or file for malicious pickle files.
    pub async fn scan(&self, path: &Path) -> Result<Vec<Finding>, CliError> {
        let path_str = path.to_string_lossy();
        let (_exit_code, stdout, _stderr) = self.bridge.run(&["--path", &path_str]).await?;

        Ok(parse_picklescan_output(&stdout))
    }
}

/// Parse picklescan text output into Findings.
pub fn parse_picklescan_output(output: &str) -> Vec<Finding> {
    lazy_static::lazy_static! {
        static ref RE: regex::Regex = regex::Regex::new(r"(?i)dangerous import of (\S+) in (.+)").unwrap();
    }
    let re = &*RE;

    output
        .lines()
        .filter_map(|line| {
            if let Some(caps) = re.captures(line) {
                let module = caps.get(1).map(|m| m.as_str()).unwrap_or("unknown");
                let file = caps.get(2).map(|m| m.as_str()).unwrap_or("");

                let mut finding = Finding::new(
                    format!("picklescan-dangerous-import-{}", module),
                    format!("PickleScan: dangerous import of {}", module),
                    Severity::High,
                );
                finding.confidence = Confidence::High;
                finding.description =
                    format!("Dangerous import of '{}' detected in pickle file", module);
                finding.cwe_ids.push(502);
                finding.tags.push("model-security".to_string());
                finding.tags.push("deserialization".to_string());
                finding.file_path = Some(std::path::PathBuf::from(file.trim()));
                finding.evidence.push(line.trim().to_string());

                Some(finding)
            } else {
                None
            }
        })
        .collect()
}