use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use uuid::Uuid;
pub mod manager;
pub mod native;
pub use manager::DefaultPluginManager;
pub use native::NativePluginLoader;
use crate::scanner::{Severity, Threat, ThreatType};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PluginId(pub String);
impl Default for PluginId {
fn default() -> Self {
Self::new()
}
}
impl PluginId {
pub fn new() -> Self {
Self(Uuid::new_v4().to_string())
}
pub const fn from_string(s: String) -> Self {
Self(s)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadata {
pub name: String,
pub version: String,
pub author: String,
pub description: String,
pub homepage: Option<String>,
pub threat_types: Vec<String>,
pub capabilities: PluginCapabilities,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PluginCapabilities {
pub scan_text: bool,
pub scan_json: bool,
pub scan_binary: bool,
pub async_scan: bool,
pub batch_scan: bool,
pub max_data_size_mb: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct ScanContext<'a> {
pub data: &'a [u8],
pub content_type: Option<&'a str>,
pub client_id: &'a str,
pub metadata: &'a HashMap<String, String>,
pub options: ScanOptions,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ScanOptions {
pub max_depth: Option<u32>,
pub timeout_ms: Option<u64>,
pub detailed: bool,
pub plugin_options: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthStatus {
pub healthy: bool,
pub message: String,
pub last_check: chrono::DateTime<chrono::Utc>,
pub metrics: PluginMetrics,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PluginMetrics {
pub scans_performed: u64,
pub threats_detected: u64,
pub avg_scan_time_us: u64,
pub errors: u64,
}
#[async_trait]
pub trait SecurityPlugin: Send + Sync {
fn metadata(&self) -> PluginMetadata;
async fn initialize(&mut self, config: serde_json::Value) -> Result<()>;
async fn scan(&self, context: ScanContext<'_>) -> Result<Vec<Threat>>;
async fn health_check(&self) -> Result<HealthStatus>;
async fn shutdown(&mut self) -> Result<()>;
async fn update_config(&mut self, config: serde_json::Value) -> Result<()> {
self.shutdown().await?;
self.initialize(config).await
}
fn get_metrics(&self) -> PluginMetrics {
PluginMetrics::default()
}
}
#[async_trait]
pub trait PluginLoader: Send + Sync {
async fn load_plugin(&self, path: &Path) -> Result<Box<dyn SecurityPlugin>>;
async fn validate_plugin(&self, path: &Path) -> Result<PluginMetadata>;
fn loader_type(&self) -> &'static str;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginInfo {
pub id: PluginId,
pub metadata: PluginMetadata,
pub enabled: bool,
pub loaded_at: chrono::DateTime<chrono::Utc>,
}
#[async_trait]
pub trait PluginManagerTrait: Send + Sync {
async fn load_plugin(&self, path: &Path) -> Result<PluginId>;
async fn unload_plugin(&self, id: &PluginId) -> Result<()>;
async fn get_plugin(&self, id: &PluginId) -> Result<PluginInfo>;
async fn list_plugins(&self) -> Result<Vec<PluginInfo>>;
async fn scan(&self, id: &PluginId, context: ScanContext<'_>) -> Result<Vec<Threat>>;
async fn scan_all(&self, context: ScanContext<'_>) -> Result<HashMap<PluginId, Vec<Threat>>>;
async fn reload_plugin(&self, id: &PluginId) -> Result<()>;
async fn get_health(&self, id: &PluginId) -> Result<HealthStatus>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginConfig {
pub enabled: bool,
pub plugin_dirs: Vec<PathBuf>,
pub auto_load: bool,
pub allowlist: Vec<String>,
pub denylist: Vec<String>,
pub max_execution_time_ms: u64,
pub isolation_level: IsolationLevel,
}
impl Default for PluginConfig {
fn default() -> Self {
Self {
enabled: false,
plugin_dirs: vec![PathBuf::from("./plugins")],
auto_load: true,
allowlist: Vec::new(),
denylist: Vec::new(),
max_execution_time_ms: 5000,
isolation_level: IsolationLevel::Standard,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum IsolationLevel {
None,
Standard,
Strong,
}
pub struct PluginHandle {
pub id: PluginId,
pub metadata: PluginMetadata,
pub plugin: Arc<dyn SecurityPlugin>,
pub enabled: bool,
pub loaded_at: chrono::DateTime<chrono::Utc>,
pub metrics: Arc<tokio::sync::RwLock<PluginMetrics>>,
}
pub trait PluginManagerFactory: Send + Sync {
fn create(&self, config: &PluginConfig) -> Result<Arc<dyn PluginManagerTrait>>;
}
pub struct DefaultPluginManagerFactory;
impl PluginManagerFactory for DefaultPluginManagerFactory {
fn create(&self, config: &PluginConfig) -> Result<Arc<dyn PluginManagerTrait>> {
if !config.enabled {
return Ok(Arc::new(NoOpPluginManager));
}
let manager = DefaultPluginManager::new(config.clone())?;
if config.auto_load {
tracing::info!("Plugin auto-loading enabled; plugins will be loaded on first use");
}
Ok(Arc::new(manager))
}
}
struct NoOpPluginManager;
#[async_trait]
impl PluginManagerTrait for NoOpPluginManager {
async fn load_plugin(&self, _path: &Path) -> Result<PluginId> {
Err(anyhow::anyhow!("Plugin system disabled"))
}
async fn unload_plugin(&self, _id: &PluginId) -> Result<()> {
Ok(())
}
async fn get_plugin(&self, _id: &PluginId) -> Result<PluginInfo> {
Err(anyhow::anyhow!("Plugin system disabled"))
}
async fn list_plugins(&self) -> Result<Vec<PluginInfo>> {
Ok(Vec::new())
}
async fn scan(&self, _id: &PluginId, _context: ScanContext<'_>) -> Result<Vec<Threat>> {
Ok(Vec::new())
}
async fn scan_all(&self, _context: ScanContext<'_>) -> Result<HashMap<PluginId, Vec<Threat>>> {
Ok(HashMap::new())
}
async fn reload_plugin(&self, _id: &PluginId) -> Result<()> {
Err(anyhow::anyhow!("Plugin system disabled"))
}
async fn get_health(&self, _id: &PluginId) -> Result<HealthStatus> {
Err(anyhow::anyhow!("Plugin system disabled"))
}
}