kindly_guard_server/plugins/
native.rs1use super::{
19 async_trait, HealthStatus, PluginCapabilities, PluginLoader, PluginMetadata, ScanContext,
20 SecurityPlugin, Severity, Threat, ThreatType,
21};
22use anyhow::Result;
23use std::path::Path;
24use tracing::{debug, info};
25
26pub struct NativePluginLoader {
28 _private: (),
29}
30
31impl Default for NativePluginLoader {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl NativePluginLoader {
38 pub const fn new() -> Self {
40 Self { _private: () }
41 }
42}
43
44#[async_trait]
45impl PluginLoader for NativePluginLoader {
46 async fn load_plugin(&self, path: &Path) -> Result<Box<dyn SecurityPlugin>> {
47 let filename = path
51 .file_name()
52 .and_then(|n| n.to_str())
53 .ok_or_else(|| anyhow::anyhow!("Invalid filename"))?;
54
55 match filename {
57 "sql_injection_plugin.so" | "sql_injection_plugin.dll" => {
58 Ok(Box::new(SqlInjectionPlugin::new()))
59 },
60 "xss_plugin.so" | "xss_plugin.dll" => Ok(Box::new(XssPlugin::new())),
61 "custom_pattern_plugin.so" | "custom_pattern_plugin.dll" => {
62 Ok(Box::new(CustomPatternPlugin::new()))
63 },
64 _ => Err(anyhow::anyhow!("Unknown plugin type: {}", filename)),
65 }
66 }
67
68 async fn validate_plugin(&self, path: &Path) -> Result<PluginMetadata> {
69 let extension = path
71 .extension()
72 .and_then(|e| e.to_str())
73 .ok_or_else(|| anyhow::anyhow!("No file extension"))?;
74
75 match extension {
76 "so" | "dll" | "dylib" => {
77 let filename = path
80 .file_stem()
81 .and_then(|n| n.to_str())
82 .ok_or_else(|| anyhow::anyhow!("Invalid filename"))?;
83
84 Ok(PluginMetadata {
85 name: filename.to_string(),
86 version: "1.0.0".to_string(),
87 author: "Plugin Author".to_string(),
88 description: format!("Native plugin: {filename}"),
89 homepage: None,
90 threat_types: vec!["custom".to_string()],
91 capabilities: PluginCapabilities {
92 scan_text: true,
93 scan_json: true,
94 scan_binary: false,
95 async_scan: true,
96 batch_scan: false,
97 max_data_size_mb: Some(10),
98 },
99 })
100 },
101 _ => Err(anyhow::anyhow!("Not a native plugin file")),
102 }
103 }
104
105 fn loader_type(&self) -> &'static str {
106 "native"
107 }
108}
109
110struct SqlInjectionPlugin {
112 patterns: Vec<regex::Regex>,
113 config: serde_json::Value,
114}
115
116impl SqlInjectionPlugin {
117 fn new() -> Self {
118 let patterns = vec![
119 regex::Regex::new(r"(?i)(\b(union|select|insert|update|delete|drop|create|alter|exec|execute)\b.*\b(from|where|table|database)\b)").unwrap(),
120 regex::Regex::new(r#"(?i)('|"|;|--|xp_|sp_)"#).unwrap(),
121 regex::Regex::new(r"(?i)(admin'|'or'|'=')").unwrap(),
122 ];
123
124 Self {
125 patterns,
126 config: serde_json::Value::Null,
127 }
128 }
129}
130
131#[async_trait]
132impl SecurityPlugin for SqlInjectionPlugin {
133 fn metadata(&self) -> PluginMetadata {
134 PluginMetadata {
135 name: "SQL Injection Detector".to_string(),
136 version: "1.0.0".to_string(),
137 author: "KindlyGuard Team".to_string(),
138 description: "Detects SQL injection attempts in text and JSON data".to_string(),
139 homepage: Some("https://kindlyguard.dev/plugins/sql-injection".to_string()),
140 threat_types: vec!["sql_injection".to_string()],
141 capabilities: PluginCapabilities {
142 scan_text: true,
143 scan_json: true,
144 scan_binary: false,
145 async_scan: true,
146 batch_scan: false,
147 max_data_size_mb: Some(10),
148 },
149 }
150 }
151
152 async fn initialize(&mut self, config: serde_json::Value) -> Result<()> {
153 self.config = config;
154 info!("SQL Injection plugin initialized");
155 Ok(())
156 }
157
158 async fn scan(&self, context: ScanContext<'_>) -> Result<Vec<Threat>> {
159 let text = String::from_utf8_lossy(context.data);
160 let mut threats = Vec::new();
161
162 for (i, pattern) in self.patterns.iter().enumerate() {
163 if let Some(m) = pattern.find(&text) {
164 threats.push(Threat {
165 threat_type: ThreatType::SqlInjection,
166 severity: Severity::High,
167 location: crate::scanner::Location::Text {
168 offset: m.start(),
169 length: m.len(),
170 },
171 description: format!("SQL injection pattern {} detected", i + 1),
172 remediation: Some("Sanitize input and use parameterized queries".to_string()),
173 });
174 }
175 }
176
177 Ok(threats)
178 }
179
180 async fn health_check(&self) -> Result<HealthStatus> {
181 Ok(HealthStatus {
182 healthy: true,
183 message: "SQL Injection plugin is healthy".to_string(),
184 last_check: chrono::Utc::now(),
185 metrics: self.get_metrics(),
186 })
187 }
188
189 async fn shutdown(&mut self) -> Result<()> {
190 info!("SQL Injection plugin shutting down");
191 Ok(())
192 }
193}
194
195struct XssPlugin {
197 patterns: Vec<regex::Regex>,
198}
199
200impl XssPlugin {
201 fn new() -> Self {
202 let patterns = vec![
203 regex::Regex::new(r"<script[^>]*>.*?</script>").unwrap(),
204 regex::Regex::new(r"javascript:").unwrap(),
205 regex::Regex::new(r"on\w+\s*=").unwrap(),
206 ];
207
208 Self { patterns }
209 }
210}
211
212#[async_trait]
213impl SecurityPlugin for XssPlugin {
214 fn metadata(&self) -> PluginMetadata {
215 PluginMetadata {
216 name: "XSS Detector".to_string(),
217 version: "1.0.0".to_string(),
218 author: "KindlyGuard Team".to_string(),
219 description: "Detects cross-site scripting attempts".to_string(),
220 homepage: None,
221 threat_types: vec!["xss".to_string()],
222 capabilities: PluginCapabilities {
223 scan_text: true,
224 scan_json: true,
225 scan_binary: false,
226 async_scan: true,
227 batch_scan: false,
228 max_data_size_mb: Some(5),
229 },
230 }
231 }
232
233 async fn initialize(&mut self, _config: serde_json::Value) -> Result<()> {
234 info!("XSS plugin initialized");
235 Ok(())
236 }
237
238 async fn scan(&self, context: ScanContext<'_>) -> Result<Vec<Threat>> {
239 let text = String::from_utf8_lossy(context.data);
240 let mut threats = Vec::new();
241
242 for pattern in &self.patterns {
243 if let Some(m) = pattern.find(&text) {
244 threats.push(Threat {
245 threat_type: ThreatType::CrossSiteScripting,
246 severity: Severity::High,
247 location: crate::scanner::Location::Text {
248 offset: m.start(),
249 length: m.len(),
250 },
251 description: "XSS pattern detected".to_string(),
252 remediation: Some("Escape HTML entities and validate input".to_string()),
253 });
254 }
255 }
256
257 Ok(threats)
258 }
259
260 async fn health_check(&self) -> Result<HealthStatus> {
261 Ok(HealthStatus {
262 healthy: true,
263 message: "XSS plugin is healthy".to_string(),
264 last_check: chrono::Utc::now(),
265 metrics: self.get_metrics(),
266 })
267 }
268
269 async fn shutdown(&mut self) -> Result<()> {
270 info!("XSS plugin shutting down");
271 Ok(())
272 }
273}
274
275struct CustomPatternPlugin {
277 patterns: Vec<(String, regex::Regex, Severity)>,
278}
279
280impl CustomPatternPlugin {
281 const fn new() -> Self {
282 Self {
283 patterns: Vec::new(),
284 }
285 }
286}
287
288#[async_trait]
289impl SecurityPlugin for CustomPatternPlugin {
290 fn metadata(&self) -> PluginMetadata {
291 PluginMetadata {
292 name: "Custom Pattern Detector".to_string(),
293 version: "1.0.0".to_string(),
294 author: "User".to_string(),
295 description: "Detects custom threat patterns".to_string(),
296 homepage: None,
297 threat_types: vec!["custom".to_string()],
298 capabilities: PluginCapabilities {
299 scan_text: true,
300 scan_json: false,
301 scan_binary: false,
302 async_scan: true,
303 batch_scan: false,
304 max_data_size_mb: Some(1),
305 },
306 }
307 }
308
309 async fn initialize(&mut self, config: serde_json::Value) -> Result<()> {
310 if let Some(patterns) = config.get("patterns").and_then(|p| p.as_array()) {
312 for pattern_config in patterns {
313 if let (Some(name), Some(regex), Some(severity)) = (
314 pattern_config.get("name").and_then(|n| n.as_str()),
315 pattern_config.get("pattern").and_then(|p| p.as_str()),
316 pattern_config.get("severity").and_then(|s| s.as_str()),
317 ) {
318 let severity = match severity {
319 "low" => Severity::Low,
320 "medium" => Severity::Medium,
321 "high" => Severity::High,
322 "critical" => Severity::Critical,
323 _ => Severity::Medium,
324 };
325
326 if let Ok(re) = regex::Regex::new(regex) {
327 self.patterns.push((name.to_string(), re, severity));
328 debug!("Added custom pattern: {}", name);
329 }
330 }
331 }
332 }
333
334 info!(
335 "Custom pattern plugin initialized with {} patterns",
336 self.patterns.len()
337 );
338 Ok(())
339 }
340
341 async fn scan(&self, context: ScanContext<'_>) -> Result<Vec<Threat>> {
342 let text = String::from_utf8_lossy(context.data);
343 let mut threats = Vec::new();
344
345 for (name, pattern, severity) in &self.patterns {
346 if let Some(m) = pattern.find(&text) {
347 threats.push(Threat {
348 threat_type: ThreatType::Custom(name.clone()),
349 severity: *severity,
350 location: crate::scanner::Location::Text {
351 offset: m.start(),
352 length: m.len(),
353 },
354 description: format!("Custom pattern '{name}' detected"),
355 remediation: None,
356 });
357 }
358 }
359
360 Ok(threats)
361 }
362
363 async fn health_check(&self) -> Result<HealthStatus> {
364 Ok(HealthStatus {
365 healthy: true,
366 message: format!(
367 "Custom pattern plugin with {} patterns",
368 self.patterns.len()
369 ),
370 last_check: chrono::Utc::now(),
371 metrics: self.get_metrics(),
372 })
373 }
374
375 async fn shutdown(&mut self) -> Result<()> {
376 info!("Custom pattern plugin shutting down");
377 self.patterns.clear();
378 Ok(())
379 }
380}