use crate::{
neutralizer::{NeutralizeResult, ThreatNeutralizer},
scanner::Threat,
security::{CommandSource, SecurityContext},
};
use anyhow::Result;
use async_trait::async_trait;
use std::sync::Arc;
pub struct SecurityAwareNeutralizer {
inner: Arc<dyn ThreatNeutralizer>,
security_context: Arc<tokio::sync::RwLock<SecurityContext>>,
}
impl SecurityAwareNeutralizer {
pub fn new(
neutralizer: Arc<dyn ThreatNeutralizer>,
security_context: Arc<tokio::sync::RwLock<SecurityContext>>,
) -> Self {
Self {
inner: neutralizer,
security_context,
}
}
pub fn with_new_context(
neutralizer: Arc<dyn ThreatNeutralizer>,
source: CommandSource,
enhanced_mode: bool,
neutralization_mode: crate::security::NeutralizationMode,
) -> Self {
let context = SecurityContext::new(source)
.with_enhanced_mode(enhanced_mode)
.with_neutralization_mode(neutralization_mode);
Self {
inner: neutralizer,
security_context: Arc::new(tokio::sync::RwLock::new(context)),
}
}
pub async fn get_context(&self) -> SecurityContext {
(*self.security_context.read().await).clone()
}
pub async fn set_user(&self, user_id: String) {
let mut context = self.security_context.write().await;
context.user_id = Some(user_id);
}
}
#[async_trait]
impl ThreatNeutralizer for SecurityAwareNeutralizer {
async fn neutralize(&self, threat: &Threat, content: &str) -> Result<NeutralizeResult> {
let should_neutralize = {
let context = self.security_context.read().await;
context.should_neutralize()
};
if !should_neutralize {
return Ok(NeutralizeResult {
action_taken: crate::neutralizer::NeutralizeAction::NoAction,
sanitized_content: None,
confidence_score: 1.0,
processing_time_us: 0,
correlation_data: None,
extracted_params: None,
});
}
tracing::info!(
"Attempting neutralization for threat {:?} in security context {}",
threat.threat_type,
self.security_context.read().await.request_id
);
let result = self.inner.neutralize(threat, content).await;
let mut context = self.security_context.write().await;
match &result {
Ok(_) => {
context.record_neutralization(true);
tracing::info!(
"Neutralization successful for request {}. Total neutralized: {}",
context.request_id,
context.neutralization.threats_neutralized
);
},
Err(e) => {
context.record_neutralization(false);
tracing::error!(
"Neutralization failed for request {}: {}. Total failures: {}",
context.request_id,
e,
context.neutralization.neutralization_failures
);
},
}
result
}
fn can_neutralize(&self, threat_type: &crate::scanner::ThreatType) -> bool {
self.inner.can_neutralize(threat_type)
}
fn get_capabilities(&self) -> crate::neutralizer::NeutralizerCapabilities {
self.inner.get_capabilities()
}
}
pub struct NeutralizationSecurityManager {
contexts: Arc<
tokio::sync::RwLock<
std::collections::HashMap<String, Arc<tokio::sync::RwLock<SecurityContext>>>,
>,
>,
}
impl Default for NeutralizationSecurityManager {
fn default() -> Self {
Self::new()
}
}
impl NeutralizationSecurityManager {
pub fn new() -> Self {
Self {
contexts: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
}
}
pub async fn create_context(
&self,
session_id: &str,
source: CommandSource,
) -> Arc<tokio::sync::RwLock<SecurityContext>> {
let context = Arc::new(tokio::sync::RwLock::new(SecurityContext::new(source)));
let mut contexts = self.contexts.write().await;
contexts.insert(session_id.to_string(), context.clone());
context
}
pub async fn get_context(
&self,
session_id: &str,
) -> Option<Arc<tokio::sync::RwLock<SecurityContext>>> {
let contexts = self.contexts.read().await;
contexts.get(session_id).cloned()
}
pub async fn remove_context(&self, session_id: &str) {
let mut contexts = self.contexts.write().await;
contexts.remove(session_id);
}
pub async fn get_summary(&self) -> NeutralizationSecuritySummary {
let contexts = self.contexts.read().await;
let mut total_neutralized = 0u32;
let mut total_failures = 0u32;
let mut active_sessions = 0usize;
for (_, context) in contexts.iter() {
let ctx = context.read().await;
total_neutralized += ctx.neutralization.threats_neutralized;
total_failures += ctx.neutralization.neutralization_failures;
active_sessions += 1;
}
NeutralizationSecuritySummary {
active_sessions,
total_neutralized,
total_failures,
overall_success_rate: if total_neutralized + total_failures > 0 {
f64::from(total_neutralized) / f64::from(total_neutralized + total_failures)
} else {
1.0
},
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct NeutralizationSecuritySummary {
pub active_sessions: usize,
pub total_neutralized: u32,
pub total_failures: u32,
pub overall_success_rate: f64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::neutralizer::standard::StandardNeutralizer;
use crate::neutralizer::NeutralizationConfig;
#[tokio::test]
async fn test_security_aware_neutralizer() {
let config = NeutralizationConfig::default();
let neutralizer = Arc::new(StandardNeutralizer::new(config));
let security_neutralizer = SecurityAwareNeutralizer::with_new_context(
neutralizer,
CommandSource::Api,
false,
crate::security::NeutralizationMode::Automatic,
);
let threat = crate::scanner::Threat {
threat_type: crate::scanner::ThreatType::SqlInjection,
severity: crate::scanner::Severity::High,
location: crate::scanner::Location::Text {
offset: 0,
length: 10,
},
description: "SQL injection detected".to_string(),
remediation: None,
};
let result = security_neutralizer
.neutralize(&threat, "test content")
.await;
assert!(result.is_ok());
let context = security_neutralizer.get_context().await;
assert_eq!(context.neutralization.threats_neutralized, 1);
}
}