use super::{CliError, CliToolBridge};
use crate::core::{Confidence, Finding, Severity};
use std::path::Path;
const BINARY: &str = "modelscan";
pub struct ModelScanBridge {
bridge: CliToolBridge,
}
impl Default for ModelScanBridge {
fn default() -> Self {
Self::new()
}
}
impl ModelScanBridge {
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(&["scan", "-p", &path_str, "--json"])
.await?;
if exit_code == 3 {
return Ok(Vec::new());
}
if exit_code == 2 {
return Err(CliError::CallFailed(format!("modelscan error: {}", stderr)));
}
Ok(parse_modelscan_output(&stdout))
}
}
pub fn parse_modelscan_output(json_text: &str) -> Vec<Finding> {
let value: serde_json::Value = match serde_json::from_str(json_text) {
Ok(v) => v,
Err(_) => return Vec::new(),
};
let issues = match value.get("issues").and_then(|v| v.as_array()) {
Some(arr) => arr,
None => return Vec::new(),
};
issues
.iter()
.map(|issue| {
let severity_str = issue
.get("severity")
.and_then(|v| v.as_str())
.unwrap_or("medium");
let severity = match severity_str.to_lowercase().as_str() {
"critical" | "cri" => Severity::Critical,
"high" | "hig" => Severity::High,
"medium" | "med" => Severity::Medium,
"low" => Severity::Low,
_ => Severity::Medium,
};
let code = issue
.get("code")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let description = issue
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("");
let source = issue.get("source").and_then(|v| v.as_str());
let mut finding = Finding::new(
format!("modelscan-{}", code),
format!("ModelScan: {}", description),
severity,
);
finding.confidence = Confidence::High;
finding.description = description.to_string();
finding.cwe_ids.push(502); finding.tags.push("model-security".to_string());
finding.tags.push("deserialization".to_string());
if let Some(src) = source {
finding.file_path = Some(std::path::PathBuf::from(src));
}
finding
})
.collect()
}