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()
}
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))
}
}
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()
}