Skip to main content

claw_guard/
config.rs

1use std::fmt;
2use std::path::PathBuf;
3
4use secrecy::SecretString;
5
6use crate::error::{GuardError, GuardResult};
7
8/// Runtime configuration for the ClawDB guard engine.
9#[derive(Clone)]
10pub struct GuardConfig {
11    /// SQLite database file path or connection string.
12    pub db_path: PathBuf,
13    /// HS256 secret used for signing and validating session JWTs.
14    pub jwt_secret: SecretString,
15    /// Optional directory containing TOML policy definitions.
16    pub policy_dir: Option<PathBuf>,
17    /// Sensitive resources that increase risk scores.
18    pub sensitive_resources: Vec<String>,
19    /// Maximum time to buffer audit entries before flushing.
20    pub audit_flush_interval_ms: u64,
21    /// Maximum number of buffered audit entries per flush.
22    pub audit_batch_size: usize,
23    /// Inclusive start of business hours in local time.
24    pub business_hours_start_hour: u8,
25    /// Exclusive end of business hours in local time.
26    pub business_hours_end_hour: u8,
27}
28
29impl fmt::Debug for GuardConfig {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        f.debug_struct("GuardConfig")
32            .field("db_path", &self.db_path)
33            .field("jwt_secret", &"***REDACTED***")
34            .field("policy_dir", &self.policy_dir)
35            .field("sensitive_resources", &self.sensitive_resources)
36            .field("audit_flush_interval_ms", &self.audit_flush_interval_ms)
37            .field("audit_batch_size", &self.audit_batch_size)
38            .field("business_hours_start_hour", &self.business_hours_start_hour)
39            .field("business_hours_end_hour", &self.business_hours_end_hour)
40            .finish()
41    }
42}
43
44impl GuardConfig {
45    /// Builds configuration from `CLAW_GUARD_*` environment variables.
46    pub fn from_env() -> GuardResult<Self> {
47        let jwt_secret = std::env::var("CLAW_GUARD_JWT_SECRET")
48            .map(|value| SecretString::new(value.into_boxed_str()))
49            .map_err(|_| GuardError::ConfigError("CLAW_GUARD_JWT_SECRET is required".to_owned()))?;
50        let db_path = std::env::var("CLAW_GUARD_DB_PATH")
51            .map(PathBuf::from)
52            .unwrap_or_else(|_| PathBuf::from("claw_guard.db"));
53        let policy_dir = std::env::var("CLAW_GUARD_POLICY_DIR")
54            .ok()
55            .map(PathBuf::from)
56            .filter(|value| !value.as_os_str().is_empty());
57        let sensitive_resources = std::env::var("CLAW_GUARD_SENSITIVE_RESOURCES")
58            .unwrap_or_default()
59            .split(',')
60            .filter_map(|value| {
61                let trimmed = value.trim();
62                (!trimmed.is_empty()).then(|| trimmed.to_owned())
63            })
64            .collect();
65
66        Ok(Self {
67            db_path,
68            jwt_secret,
69            policy_dir,
70            sensitive_resources,
71            audit_flush_interval_ms: parse_or_default("CLAW_GUARD_AUDIT_FLUSH_INTERVAL_MS", 100)?,
72            audit_batch_size: parse_or_default("CLAW_GUARD_AUDIT_BATCH_SIZE", 500)?,
73            business_hours_start_hour: parse_or_default("CLAW_GUARD_BUSINESS_HOURS_START", 8)?,
74            business_hours_end_hour: parse_or_default("CLAW_GUARD_BUSINESS_HOURS_END", 18)?,
75        })
76    }
77
78    /// Returns a SQLite connection string for `sqlx`.
79    pub fn sqlite_connection_string(&self) -> String {
80        let path = self.db_path.to_string_lossy();
81        if path == ":memory:" {
82            "sqlite::memory:".to_owned()
83        } else if path.starts_with("sqlite:") {
84            path.into_owned()
85        } else {
86            format!("sqlite://{}?mode=rwc", path)
87        }
88    }
89}
90
91fn parse_or_default<T>(name: &str, default: T) -> GuardResult<T>
92where
93    T: std::str::FromStr,
94    T::Err: fmt::Display,
95{
96    match std::env::var(name) {
97        Ok(value) => value
98            .parse::<T>()
99            .map_err(|error| GuardError::ConfigError(format!("invalid {name}: {error}"))),
100        Err(_) => Ok(default),
101    }
102}