Skip to main content

kindly_guard_server/neutralizer/
security_aware.rs

1// Copyright 2025 Kindly Software Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Security context-aware neutralization
15//!
16//! Integrates neutralization with the security context to provide
17//! comprehensive threat tracking and security decisions.
18
19use crate::{
20    neutralizer::{NeutralizeResult, ThreatNeutralizer},
21    scanner::Threat,
22    security::{CommandSource, SecurityContext},
23};
24use anyhow::Result;
25use async_trait::async_trait;
26use std::sync::Arc;
27
28/// Security-aware neutralizer that tracks operations in security context
29pub struct SecurityAwareNeutralizer {
30    inner: Arc<dyn ThreatNeutralizer>,
31    security_context: Arc<tokio::sync::RwLock<SecurityContext>>,
32}
33
34impl SecurityAwareNeutralizer {
35    pub fn new(
36        neutralizer: Arc<dyn ThreatNeutralizer>,
37        security_context: Arc<tokio::sync::RwLock<SecurityContext>>,
38    ) -> Self {
39        Self {
40            inner: neutralizer,
41            security_context,
42        }
43    }
44
45    /// Create with a new security context
46    pub fn with_new_context(
47        neutralizer: Arc<dyn ThreatNeutralizer>,
48        source: CommandSource,
49        enhanced_mode: bool,
50        neutralization_mode: crate::security::NeutralizationMode,
51    ) -> Self {
52        let context = SecurityContext::new(source)
53            .with_enhanced_mode(enhanced_mode)
54            .with_neutralization_mode(neutralization_mode);
55
56        Self {
57            inner: neutralizer,
58            security_context: Arc::new(tokio::sync::RwLock::new(context)),
59        }
60    }
61
62    /// Get the security context
63    pub async fn get_context(&self) -> SecurityContext {
64        (*self.security_context.read().await).clone()
65    }
66
67    /// Update security context user
68    pub async fn set_user(&self, user_id: String) {
69        let mut context = self.security_context.write().await;
70        context.user_id = Some(user_id);
71    }
72}
73
74#[async_trait]
75impl ThreatNeutralizer for SecurityAwareNeutralizer {
76    async fn neutralize(&self, threat: &Threat, content: &str) -> Result<NeutralizeResult> {
77        // Check if neutralization should be attempted
78        let should_neutralize = {
79            let context = self.security_context.read().await;
80            context.should_neutralize()
81        };
82
83        if !should_neutralize {
84            // Return no-action result if neutralization is disabled
85            return Ok(NeutralizeResult {
86                action_taken: crate::neutralizer::NeutralizeAction::NoAction,
87                sanitized_content: None,
88                confidence_score: 1.0,
89                processing_time_us: 0,
90                correlation_data: None,
91                extracted_params: None,
92            });
93        }
94
95        // Log the neutralization attempt
96        tracing::info!(
97            "Attempting neutralization for threat {:?} in security context {}",
98            threat.threat_type,
99            self.security_context.read().await.request_id
100        );
101
102        // Perform neutralization
103        let result = self.inner.neutralize(threat, content).await;
104
105        // Update security context based on result
106        let mut context = self.security_context.write().await;
107        match &result {
108            Ok(_) => {
109                context.record_neutralization(true);
110                tracing::info!(
111                    "Neutralization successful for request {}. Total neutralized: {}",
112                    context.request_id,
113                    context.neutralization.threats_neutralized
114                );
115            },
116            Err(e) => {
117                context.record_neutralization(false);
118                tracing::error!(
119                    "Neutralization failed for request {}: {}. Total failures: {}",
120                    context.request_id,
121                    e,
122                    context.neutralization.neutralization_failures
123                );
124            },
125        }
126
127        result
128    }
129
130    fn can_neutralize(&self, threat_type: &crate::scanner::ThreatType) -> bool {
131        self.inner.can_neutralize(threat_type)
132    }
133
134    fn get_capabilities(&self) -> crate::neutralizer::NeutralizerCapabilities {
135        self.inner.get_capabilities()
136    }
137}
138
139/// Security context manager for neutralization operations
140pub struct NeutralizationSecurityManager {
141    contexts: Arc<
142        tokio::sync::RwLock<
143            std::collections::HashMap<String, Arc<tokio::sync::RwLock<SecurityContext>>>,
144        >,
145    >,
146}
147
148impl Default for NeutralizationSecurityManager {
149    fn default() -> Self {
150        Self::new()
151    }
152}
153
154impl NeutralizationSecurityManager {
155    pub fn new() -> Self {
156        Self {
157            contexts: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
158        }
159    }
160
161    /// Create a new security context for a session
162    pub async fn create_context(
163        &self,
164        session_id: &str,
165        source: CommandSource,
166    ) -> Arc<tokio::sync::RwLock<SecurityContext>> {
167        let context = Arc::new(tokio::sync::RwLock::new(SecurityContext::new(source)));
168
169        let mut contexts = self.contexts.write().await;
170        contexts.insert(session_id.to_string(), context.clone());
171
172        context
173    }
174
175    /// Get context for a session
176    pub async fn get_context(
177        &self,
178        session_id: &str,
179    ) -> Option<Arc<tokio::sync::RwLock<SecurityContext>>> {
180        let contexts = self.contexts.read().await;
181        contexts.get(session_id).cloned()
182    }
183
184    /// Remove context when session ends
185    pub async fn remove_context(&self, session_id: &str) {
186        let mut contexts = self.contexts.write().await;
187        contexts.remove(session_id);
188    }
189
190    /// Get summary of all active contexts
191    pub async fn get_summary(&self) -> NeutralizationSecuritySummary {
192        let contexts = self.contexts.read().await;
193
194        let mut total_neutralized = 0u32;
195        let mut total_failures = 0u32;
196        let mut active_sessions = 0usize;
197
198        for (_, context) in contexts.iter() {
199            let ctx = context.read().await;
200            total_neutralized += ctx.neutralization.threats_neutralized;
201            total_failures += ctx.neutralization.neutralization_failures;
202            active_sessions += 1;
203        }
204
205        NeutralizationSecuritySummary {
206            active_sessions,
207            total_neutralized,
208            total_failures,
209            overall_success_rate: if total_neutralized + total_failures > 0 {
210                f64::from(total_neutralized) / f64::from(total_neutralized + total_failures)
211            } else {
212                1.0
213            },
214        }
215    }
216}
217
218/// Summary of neutralization security status
219#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
220pub struct NeutralizationSecuritySummary {
221    pub active_sessions: usize,
222    pub total_neutralized: u32,
223    pub total_failures: u32,
224    pub overall_success_rate: f64,
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::neutralizer::standard::StandardNeutralizer;
231    use crate::neutralizer::NeutralizationConfig;
232
233    #[tokio::test]
234    async fn test_security_aware_neutralizer() {
235        let config = NeutralizationConfig::default();
236        let neutralizer = Arc::new(StandardNeutralizer::new(config));
237
238        let security_neutralizer = SecurityAwareNeutralizer::with_new_context(
239            neutralizer,
240            CommandSource::Api,
241            false,
242            crate::security::NeutralizationMode::Automatic,
243        );
244
245        // Should allow neutralization in automatic mode
246        let threat = crate::scanner::Threat {
247            threat_type: crate::scanner::ThreatType::SqlInjection,
248            severity: crate::scanner::Severity::High,
249            location: crate::scanner::Location::Text {
250                offset: 0,
251                length: 10,
252            },
253            description: "SQL injection detected".to_string(),
254            remediation: None,
255        };
256
257        let result = security_neutralizer
258            .neutralize(&threat, "test content")
259            .await;
260        assert!(result.is_ok());
261
262        // Check context was updated
263        let context = security_neutralizer.get_context().await;
264        assert_eq!(context.neutralization.threats_neutralized, 1);
265    }
266}