kindly_guard_server/plugins/
mod.rs1use anyhow::Result;
21use async_trait::async_trait;
22use serde::{Deserialize, Serialize};
23use std::collections::HashMap;
24use std::path::{Path, PathBuf};
25use std::sync::Arc;
26use uuid::Uuid;
27
28pub mod manager;
29pub mod native;
30pub use manager::DefaultPluginManager;
36pub use native::NativePluginLoader;
37use crate::scanner::{Severity, Threat, ThreatType};
41
42#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
44pub struct PluginId(pub String);
45
46impl Default for PluginId {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl PluginId {
53 pub fn new() -> Self {
55 Self(Uuid::new_v4().to_string())
56 }
57
58 pub const fn from_string(s: String) -> Self {
60 Self(s)
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct PluginMetadata {
67 pub name: String,
69 pub version: String,
71 pub author: String,
73 pub description: String,
75 pub homepage: Option<String>,
77 pub threat_types: Vec<String>,
79 pub capabilities: PluginCapabilities,
81}
82
83#[derive(Debug, Clone, Default, Serialize, Deserialize)]
85pub struct PluginCapabilities {
86 pub scan_text: bool,
88 pub scan_json: bool,
90 pub scan_binary: bool,
92 pub async_scan: bool,
94 pub batch_scan: bool,
96 pub max_data_size_mb: Option<u32>,
98}
99
100#[derive(Debug, Clone)]
102pub struct ScanContext<'a> {
103 pub data: &'a [u8],
105 pub content_type: Option<&'a str>,
107 pub client_id: &'a str,
109 pub metadata: &'a HashMap<String, String>,
111 pub options: ScanOptions,
113}
114
115#[derive(Debug, Clone, Default, Serialize, Deserialize)]
117pub struct ScanOptions {
118 pub max_depth: Option<u32>,
120 pub timeout_ms: Option<u64>,
122 pub detailed: bool,
124 pub plugin_options: HashMap<String, serde_json::Value>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct HealthStatus {
131 pub healthy: bool,
133 pub message: String,
135 pub last_check: chrono::DateTime<chrono::Utc>,
137 pub metrics: PluginMetrics,
139}
140
141#[derive(Debug, Clone, Default, Serialize, Deserialize)]
143pub struct PluginMetrics {
144 pub scans_performed: u64,
146 pub threats_detected: u64,
148 pub avg_scan_time_us: u64,
150 pub errors: u64,
152}
153
154#[async_trait]
156pub trait SecurityPlugin: Send + Sync {
157 fn metadata(&self) -> PluginMetadata;
159
160 async fn initialize(&mut self, config: serde_json::Value) -> Result<()>;
162
163 async fn scan(&self, context: ScanContext<'_>) -> Result<Vec<Threat>>;
165
166 async fn health_check(&self) -> Result<HealthStatus>;
168
169 async fn shutdown(&mut self) -> Result<()>;
171
172 async fn update_config(&mut self, config: serde_json::Value) -> Result<()> {
174 self.shutdown().await?;
176 self.initialize(config).await
177 }
178
179 fn get_metrics(&self) -> PluginMetrics {
181 PluginMetrics::default()
182 }
183}
184
185#[async_trait]
187pub trait PluginLoader: Send + Sync {
188 async fn load_plugin(&self, path: &Path) -> Result<Box<dyn SecurityPlugin>>;
190
191 async fn validate_plugin(&self, path: &Path) -> Result<PluginMetadata>;
193
194 fn loader_type(&self) -> &'static str;
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct PluginInfo {
201 pub id: PluginId,
202 pub metadata: PluginMetadata,
203 pub enabled: bool,
204 pub loaded_at: chrono::DateTime<chrono::Utc>,
205}
206
207#[async_trait]
209pub trait PluginManagerTrait: Send + Sync {
210 async fn load_plugin(&self, path: &Path) -> Result<PluginId>;
212
213 async fn unload_plugin(&self, id: &PluginId) -> Result<()>;
215
216 async fn get_plugin(&self, id: &PluginId) -> Result<PluginInfo>;
218
219 async fn list_plugins(&self) -> Result<Vec<PluginInfo>>;
221
222 async fn scan(&self, id: &PluginId, context: ScanContext<'_>) -> Result<Vec<Threat>>;
224
225 async fn scan_all(&self, context: ScanContext<'_>) -> Result<HashMap<PluginId, Vec<Threat>>>;
227
228 async fn reload_plugin(&self, id: &PluginId) -> Result<()>;
230
231 async fn get_health(&self, id: &PluginId) -> Result<HealthStatus>;
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct PluginConfig {
238 pub enabled: bool,
240 pub plugin_dirs: Vec<PathBuf>,
242 pub auto_load: bool,
244 pub allowlist: Vec<String>,
246 pub denylist: Vec<String>,
248 pub max_execution_time_ms: u64,
253 pub isolation_level: IsolationLevel,
255}
256
257impl Default for PluginConfig {
258 fn default() -> Self {
259 Self {
260 enabled: false,
261 plugin_dirs: vec![PathBuf::from("./plugins")],
262 auto_load: true,
263 allowlist: Vec::new(),
264 denylist: Vec::new(),
265 max_execution_time_ms: 5000,
268 isolation_level: IsolationLevel::Standard,
269 }
270 }
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
275#[serde(rename_all = "lowercase")]
276pub enum IsolationLevel {
277 None,
279 Standard,
281 Strong,
283}
284
285pub struct PluginHandle {
287 pub id: PluginId,
288 pub metadata: PluginMetadata,
289 pub plugin: Arc<dyn SecurityPlugin>,
290 pub enabled: bool,
291 pub loaded_at: chrono::DateTime<chrono::Utc>,
292 pub metrics: Arc<tokio::sync::RwLock<PluginMetrics>>,
293}
294
295pub trait PluginManagerFactory: Send + Sync {
297 fn create(&self, config: &PluginConfig) -> Result<Arc<dyn PluginManagerTrait>>;
299}
300
301pub struct DefaultPluginManagerFactory;
303
304impl PluginManagerFactory for DefaultPluginManagerFactory {
305 fn create(&self, config: &PluginConfig) -> Result<Arc<dyn PluginManagerTrait>> {
306 if !config.enabled {
307 return Ok(Arc::new(NoOpPluginManager));
309 }
310
311 let manager = DefaultPluginManager::new(config.clone())?;
312
313 if config.auto_load {
316 tracing::info!("Plugin auto-loading enabled; plugins will be loaded on first use");
317 }
318
319 Ok(Arc::new(manager))
320 }
321}
322
323struct NoOpPluginManager;
325
326#[async_trait]
327impl PluginManagerTrait for NoOpPluginManager {
328 async fn load_plugin(&self, _path: &Path) -> Result<PluginId> {
329 Err(anyhow::anyhow!("Plugin system disabled"))
330 }
331
332 async fn unload_plugin(&self, _id: &PluginId) -> Result<()> {
333 Ok(())
334 }
335
336 async fn get_plugin(&self, _id: &PluginId) -> Result<PluginInfo> {
337 Err(anyhow::anyhow!("Plugin system disabled"))
338 }
339
340 async fn list_plugins(&self) -> Result<Vec<PluginInfo>> {
341 Ok(Vec::new())
342 }
343
344 async fn scan(&self, _id: &PluginId, _context: ScanContext<'_>) -> Result<Vec<Threat>> {
345 Ok(Vec::new())
346 }
347
348 async fn scan_all(&self, _context: ScanContext<'_>) -> Result<HashMap<PluginId, Vec<Threat>>> {
349 Ok(HashMap::new())
350 }
351
352 async fn reload_plugin(&self, _id: &PluginId) -> Result<()> {
353 Err(anyhow::anyhow!("Plugin system disabled"))
354 }
355
356 async fn get_health(&self, _id: &PluginId) -> Result<HealthStatus> {
357 Err(anyhow::anyhow!("Plugin system disabled"))
358 }
359}