Skip to main content

zeph_config/
security.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use serde::{Deserialize, Serialize};
5use zeph_tools::AutonomyLevel;
6use zeph_tools::PreExecutionVerifierConfig;
7use zeph_tools::TrustLevel;
8
9use crate::defaults::default_true;
10use crate::rate_limit::RateLimitConfig;
11use crate::sanitizer::{
12    ContentIsolationConfig, ExfiltrationGuardConfig, MemoryWriteValidationConfig, PiiFilterConfig,
13    ResponseVerificationConfig,
14};
15
16#[cfg(feature = "guardrail")]
17use crate::sanitizer::GuardrailConfig;
18
19fn default_trust_default_level() -> TrustLevel {
20    TrustLevel::Quarantined
21}
22
23fn default_trust_local_level() -> TrustLevel {
24    TrustLevel::Trusted
25}
26
27fn default_trust_hash_mismatch_level() -> TrustLevel {
28    TrustLevel::Quarantined
29}
30
31fn default_llm_timeout() -> u64 {
32    120
33}
34
35fn default_embedding_timeout() -> u64 {
36    30
37}
38
39fn default_a2a_timeout() -> u64 {
40    30
41}
42
43fn default_max_parallel_tools() -> usize {
44    8
45}
46
47fn default_llm_request_timeout() -> u64 {
48    600
49}
50
51#[derive(Debug, Clone, Deserialize, Serialize)]
52pub struct TrustConfig {
53    #[serde(default = "default_trust_default_level")]
54    pub default_level: TrustLevel,
55    #[serde(default = "default_trust_local_level")]
56    pub local_level: TrustLevel,
57    #[serde(default = "default_trust_hash_mismatch_level")]
58    pub hash_mismatch_level: TrustLevel,
59    /// Scan skill body content for injection patterns at load time.
60    ///
61    /// When `true`, `SkillRegistry::scan_loaded()` is called at agent startup.
62    /// This is **advisory only** — scan results are logged as warnings and do not
63    /// automatically change trust levels or block tool calls.
64    ///
65    /// Defaults to `true` (secure by default).
66    #[serde(default = "default_true")]
67    pub scan_on_load: bool,
68}
69
70impl Default for TrustConfig {
71    fn default() -> Self {
72        Self {
73            default_level: default_trust_default_level(),
74            local_level: default_trust_local_level(),
75            hash_mismatch_level: default_trust_hash_mismatch_level(),
76            scan_on_load: true,
77        }
78    }
79}
80
81#[derive(Debug, Clone, Deserialize, Serialize)]
82pub struct SecurityConfig {
83    #[serde(default = "default_true")]
84    pub redact_secrets: bool,
85    #[serde(default)]
86    pub autonomy_level: AutonomyLevel,
87    #[serde(default)]
88    pub content_isolation: ContentIsolationConfig,
89    #[serde(default)]
90    pub exfiltration_guard: ExfiltrationGuardConfig,
91    /// Memory write validation (enabled by default).
92    #[serde(default)]
93    pub memory_validation: MemoryWriteValidationConfig,
94    /// PII filter for tool outputs and debug dumps (opt-in, disabled by default).
95    #[serde(default)]
96    pub pii_filter: PiiFilterConfig,
97    /// Tool action rate limiter (opt-in, disabled by default).
98    #[serde(default)]
99    pub rate_limit: RateLimitConfig,
100    /// Pre-execution verifiers (enabled by default).
101    #[serde(default)]
102    pub pre_execution_verify: PreExecutionVerifierConfig,
103    /// LLM-based prompt injection pre-screener (opt-in, disabled by default).
104    #[cfg(feature = "guardrail")]
105    #[serde(default)]
106    pub guardrail: GuardrailConfig,
107    /// Post-LLM response verification layer (enabled by default).
108    #[serde(default)]
109    pub response_verification: ResponseVerificationConfig,
110}
111
112impl Default for SecurityConfig {
113    fn default() -> Self {
114        Self {
115            redact_secrets: true,
116            autonomy_level: AutonomyLevel::default(),
117            content_isolation: ContentIsolationConfig::default(),
118            exfiltration_guard: ExfiltrationGuardConfig::default(),
119            memory_validation: MemoryWriteValidationConfig::default(),
120            pii_filter: PiiFilterConfig::default(),
121            rate_limit: RateLimitConfig::default(),
122            pre_execution_verify: PreExecutionVerifierConfig::default(),
123            #[cfg(feature = "guardrail")]
124            guardrail: GuardrailConfig::default(),
125            response_verification: ResponseVerificationConfig::default(),
126        }
127    }
128}
129
130#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
131pub struct TimeoutConfig {
132    #[serde(default = "default_llm_timeout")]
133    pub llm_seconds: u64,
134    #[serde(default = "default_llm_request_timeout")]
135    pub llm_request_timeout_secs: u64,
136    #[serde(default = "default_embedding_timeout")]
137    pub embedding_seconds: u64,
138    #[serde(default = "default_a2a_timeout")]
139    pub a2a_seconds: u64,
140    #[serde(default = "default_max_parallel_tools")]
141    pub max_parallel_tools: usize,
142}
143
144impl Default for TimeoutConfig {
145    fn default() -> Self {
146        Self {
147            llm_seconds: default_llm_timeout(),
148            llm_request_timeout_secs: default_llm_request_timeout(),
149            embedding_seconds: default_embedding_timeout(),
150            a2a_seconds: default_a2a_timeout(),
151            max_parallel_tools: default_max_parallel_tools(),
152        }
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn trust_config_default_has_scan_on_load_true() {
162        let config = TrustConfig::default();
163        assert!(config.scan_on_load);
164    }
165
166    #[test]
167    fn trust_config_serde_roundtrip_with_scan_on_load() {
168        let config = TrustConfig {
169            default_level: TrustLevel::Quarantined,
170            local_level: TrustLevel::Trusted,
171            hash_mismatch_level: TrustLevel::Quarantined,
172            scan_on_load: false,
173        };
174        let toml = toml::to_string(&config).expect("serialize");
175        let deserialized: TrustConfig = toml::from_str(&toml).expect("deserialize");
176        assert!(!deserialized.scan_on_load);
177    }
178
179    #[test]
180    fn trust_config_missing_scan_on_load_defaults_to_true() {
181        let toml = r#"
182default_level = "quarantined"
183local_level = "trusted"
184hash_mismatch_level = "quarantined"
185"#;
186        let config: TrustConfig = toml::from_str(toml).expect("deserialize");
187        assert!(
188            config.scan_on_load,
189            "missing scan_on_load must default to true"
190        );
191    }
192}