use anyhow::Result;
use base64::{engine::general_purpose, Engine as _};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub mod reload;
use crate::audit::AuditConfig;
use crate::auth::AuthConfig;
#[cfg(feature = "enhanced")]
use crate::event_processor::EventProcessorConfig;
use crate::neutralizer::NeutralizationConfig;
use crate::plugins::PluginConfig;
use crate::rate_limit::RateLimitConfig;
use crate::resilience::config::ResilienceConfig;
use crate::signing::SigningConfig;
use crate::storage::StorageConfig;
use crate::telemetry::TelemetryConfig;
use crate::transport::TransportConfig;
#[cfg(not(feature = "enhanced"))]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct EventProcessorConfig {
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub server: ServerConfig,
pub scanner: ScannerConfig,
pub shield: ShieldConfig,
pub auth: AuthConfig,
pub signing: SigningConfig,
pub rate_limit: RateLimitConfig,
pub event_processor: EventProcessorConfig,
pub telemetry: TelemetryConfig,
pub storage: StorageConfig,
pub plugins: PluginConfig,
pub audit: AuditConfig,
pub transport: TransportConfig,
pub resilience: ResilienceConfig,
pub neutralization: NeutralizationConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub neutralizer: Option<NeutralizationConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
#[serde(default = "default_port")]
pub port: u16,
#[serde(default = "default_true")]
pub stdio: bool,
#[serde(default = "default_max_connections")]
pub max_connections: usize,
#[serde(default = "default_timeout")]
pub request_timeout_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScannerConfig {
#[serde(default = "default_true")]
pub unicode_detection: bool,
#[serde(default = "default_true")]
pub injection_detection: bool,
#[serde(default = "default_true")]
pub path_traversal_detection: bool,
#[serde(default = "default_some_true")]
pub xss_detection: Option<bool>,
#[serde(default = "default_true")]
pub crypto_detection: bool,
#[serde(default = "default_some_false")]
pub enhanced_mode: Option<bool>,
pub custom_patterns: Option<PathBuf>,
#[serde(default = "default_max_depth")]
pub max_scan_depth: usize,
#[serde(default = "default_false")]
pub enable_event_buffer: bool,
#[serde(default = "default_max_content_size")]
pub max_content_size: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_input_size: Option<usize>,
#[serde(default = "default_false")]
pub allow_text_control_chars: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShieldConfig {
#[serde(default = "default_false")]
pub enabled: bool,
#[serde(default = "default_update_interval")]
pub update_interval_ms: u64,
#[serde(default = "default_false")]
pub detailed_stats: bool,
#[serde(default = "default_true")]
pub color: bool,
}
impl Config {
pub const fn is_event_processor_enabled(&self) -> bool {
#[cfg(feature = "enhanced")]
return self.event_processor.enabled;
#[cfg(not(feature = "enhanced"))]
return false;
}
pub fn neutralizer(&self) -> &NeutralizationConfig {
self.neutralizer.as_ref().unwrap_or(&self.neutralization)
}
pub fn load() -> Result<Self> {
let config_path = std::env::var("KINDLY_GUARD_CONFIG")
.map_or_else(|_| PathBuf::from("kindly-guard.toml"), PathBuf::from);
if config_path.exists() {
Self::load_from_file(&config_path.to_string_lossy())
} else {
Ok(Self::default())
}
}
pub fn load_from_file(path: &str) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: Self = toml::from_str(&content)?;
Ok(config)
}
pub fn validate_security(&self) -> Result<()> {
if !self.auth.enabled {
tracing::warn!("Authentication is disabled - this is insecure for production!");
}
if !self.rate_limit.enabled {
tracing::warn!("Rate limiting is disabled - vulnerable to DoS attacks!");
}
if let Some(ref secret) = self.auth.jwt_secret {
let decoded = general_purpose::STANDARD.decode(secret)?;
if decoded.len() < 32 {
return Err(anyhow::anyhow!(
"JWT secret too short - use at least 256 bits (32 bytes)"
));
}
}
if !self.scanner.unicode_detection
|| !self.scanner.injection_detection
|| !self.scanner.path_traversal_detection
{
tracing::warn!("Some threat detections are disabled - reduced security coverage");
}
Ok(())
}
}
impl Default for Config {
fn default() -> Self {
Self {
server: ServerConfig {
port: default_port(),
stdio: default_true(),
max_connections: default_max_connections(),
request_timeout_secs: default_timeout(),
},
scanner: ScannerConfig {
unicode_detection: default_true(),
injection_detection: default_true(),
path_traversal_detection: default_true(),
xss_detection: Some(true),
crypto_detection: default_true(),
enhanced_mode: Some(false),
custom_patterns: None,
max_scan_depth: default_max_depth(),
enable_event_buffer: default_false(),
max_content_size: default_max_content_size(),
max_input_size: None,
allow_text_control_chars: default_false(),
},
shield: ShieldConfig {
enabled: default_false(),
update_interval_ms: default_update_interval(),
detailed_stats: default_false(),
color: default_true(),
},
auth: AuthConfig::default(),
signing: SigningConfig::default(),
rate_limit: RateLimitConfig::default(),
event_processor: EventProcessorConfig::default(),
telemetry: TelemetryConfig::default(),
storage: StorageConfig::default(),
plugins: PluginConfig::default(),
audit: AuditConfig::default(),
transport: TransportConfig::default(),
resilience: ResilienceConfig::default(),
neutralization: NeutralizationConfig::default(),
neutralizer: None,
}
}
}
impl Default for ShieldConfig {
fn default() -> Self {
Self {
enabled: default_false(),
update_interval_ms: default_update_interval(),
detailed_stats: default_false(),
color: default_true(),
}
}
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
port: default_port(),
stdio: default_true(),
max_connections: default_max_connections(),
request_timeout_secs: default_timeout(),
}
}
}
impl Default for ScannerConfig {
fn default() -> Self {
Self {
unicode_detection: default_true(),
injection_detection: default_true(),
path_traversal_detection: default_true(),
xss_detection: default_some_true(),
crypto_detection: default_true(),
enhanced_mode: default_some_false(),
custom_patterns: None,
max_scan_depth: default_max_depth(),
enable_event_buffer: default_false(),
max_content_size: default_max_content_size(),
max_input_size: None,
allow_text_control_chars: default_false(),
}
}
}
const fn default_port() -> u16 {
8080
}
const fn default_true() -> bool {
true
}
const fn default_false() -> bool {
false
}
fn default_some_true() -> Option<bool> {
Some(true)
}
fn default_some_false() -> Option<bool> {
Some(false)
}
const fn default_max_connections() -> usize {
100
}
const fn default_max_depth() -> usize {
10
}
const fn default_update_interval() -> u64 {
1000
}
const fn default_timeout() -> u64 {
30
}
const fn default_max_content_size() -> usize {
5 * 1024 * 1024 }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert!(config.server.stdio);
assert!(config.scanner.unicode_detection);
assert_eq!(config.server.port, 8080);
}
#[test]
fn test_security_validation() {
let mut config = Config::default();
assert!(config.validate_security().is_ok());
config.auth.jwt_secret = Some("c2hvcnQ=".to_string()); assert!(config.validate_security().is_err());
}
}