1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7#[serde(rename_all = "snake_case")]
8pub enum AirlockMode {
9 Enforce,
11 #[default]
13 Shadow,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct AirlockConfig {
19 #[serde(default)]
21 pub mode: AirlockMode,
22
23 #[serde(default)]
25 pub rce: RceConfig,
26
27 #[serde(default)]
29 pub velocity: VelocityConfig,
30
31 #[serde(default)]
33 pub exfiltration: ExfiltrationConfig,
34
35 #[serde(default)]
37 pub schema_drift: SchemaDriftConfig,
38
39 #[serde(default)]
41 pub behavioral_drift: BehavioralDriftConfig,
42}
43
44impl Default for AirlockConfig {
45 fn default() -> Self {
46 Self {
47 mode: AirlockMode::Shadow,
48 rce: RceConfig::default(),
49 velocity: VelocityConfig::default(),
50 exfiltration: ExfiltrationConfig::default(),
51 schema_drift: SchemaDriftConfig::default(),
52 behavioral_drift: BehavioralDriftConfig::default(),
53 }
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct RceConfig {
60 #[serde(default = "default_true")]
62 pub enabled: bool,
63
64 #[serde(default = "default_rce_tools")]
66 pub target_tools: Vec<String>,
67
68 #[serde(default)]
70 pub custom_patterns: Vec<String>,
71}
72
73impl Default for RceConfig {
74 fn default() -> Self {
75 Self {
76 enabled: true,
77 target_tools: default_rce_tools(),
78 custom_patterns: Vec::new(),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct VelocityConfig {
86 #[serde(default = "default_true")]
88 pub enabled: bool,
89
90 #[serde(default = "default_max_cost_cents")]
92 pub max_cost_cents: u64,
93
94 #[serde(default = "default_window_seconds")]
96 pub window_seconds: u64,
97
98 #[serde(default = "default_loop_threshold")]
100 pub loop_threshold: u32,
101}
102
103impl Default for VelocityConfig {
104 fn default() -> Self {
105 Self {
106 enabled: true,
107 max_cost_cents: default_max_cost_cents(),
108 window_seconds: default_window_seconds(),
109 loop_threshold: default_loop_threshold(),
110 }
111 }
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct ExfiltrationConfig {
117 #[serde(default = "default_true")]
119 pub enabled: bool,
120
121 #[serde(default = "default_network_tools")]
123 pub target_tools: Vec<String>,
124
125 #[serde(default)]
127 pub allowed_domains: Vec<String>,
128
129 #[serde(default = "default_true")]
131 pub block_ip_addresses: bool,
132
133 #[serde(default = "default_true")]
136 pub credential_dlp_enabled: bool,
137
138 #[serde(default)]
142 pub data_budget_per_domain_bytes: Option<u64>,
143}
144
145impl Default for ExfiltrationConfig {
146 fn default() -> Self {
147 Self {
148 enabled: true,
149 target_tools: default_network_tools(),
150 allowed_domains: Vec::new(),
151 block_ip_addresses: true,
152 credential_dlp_enabled: true,
153 data_budget_per_domain_bytes: None,
154 }
155 }
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct SchemaDriftConfig {
161 #[serde(default = "default_true")]
163 pub enabled: bool,
164
165 #[serde(default = "default_schema_drift_risk_score")]
167 pub risk_score: u8,
168
169 #[serde(default = "default_schema_drift_fail_closed")]
183 pub fail_closed_on_unregistered: bool,
184}
185
186impl Default for SchemaDriftConfig {
187 fn default() -> Self {
188 Self {
189 enabled: true,
190 risk_score: default_schema_drift_risk_score(),
191 fail_closed_on_unregistered: default_schema_drift_fail_closed(),
192 }
193 }
194}
195
196fn default_schema_drift_risk_score() -> u8 {
197 70
198}
199
200fn default_schema_drift_fail_closed() -> bool {
201 false
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct BehavioralDriftConfig {
207 #[serde(default = "default_true")]
209 pub enabled: bool,
210
211 #[serde(default = "default_behavioral_window")]
213 pub window_size: usize,
214
215 #[serde(default = "default_behavioral_min_observations")]
218 pub min_observations: usize,
219
220 #[serde(default = "default_behavioral_z_threshold")]
224 pub z_threshold: f32,
225
226 #[serde(default = "default_behavioral_risk_score")]
228 pub risk_score: u8,
229}
230
231impl Default for BehavioralDriftConfig {
232 fn default() -> Self {
233 Self {
234 enabled: true,
235 window_size: default_behavioral_window(),
236 min_observations: default_behavioral_min_observations(),
237 z_threshold: default_behavioral_z_threshold(),
238 risk_score: default_behavioral_risk_score(),
239 }
240 }
241}
242
243fn default_behavioral_window() -> usize {
244 100
245}
246
247fn default_behavioral_min_observations() -> usize {
248 20
249}
250
251fn default_behavioral_z_threshold() -> f32 {
252 3.0
253}
254
255fn default_behavioral_risk_score() -> u8 {
256 65
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct CoherenceConfig {
270 #[serde(default = "default_true")]
272 pub enabled: bool,
273
274 #[serde(default = "default_coherence_lookahead")]
278 pub lookahead: usize,
279
280 #[serde(default = "default_coherence_risk_score")]
283 pub risk_score: u8,
284
285 #[serde(default = "default_coherence_min_confidence")]
288 pub min_confidence: f64,
289}
290
291impl Default for CoherenceConfig {
292 fn default() -> Self {
293 Self {
294 enabled: true,
295 lookahead: default_coherence_lookahead(),
296 risk_score: default_coherence_risk_score(),
297 min_confidence: default_coherence_min_confidence(),
298 }
299 }
300}
301
302fn default_coherence_lookahead() -> usize {
303 8
304}
305
306fn default_coherence_risk_score() -> u8 {
307 70
308}
309
310fn default_coherence_min_confidence() -> f64 {
311 0.5
312}
313
314fn default_true() -> bool {
319 true
320}
321
322fn default_max_cost_cents() -> u64 {
323 100 }
325
326fn default_window_seconds() -> u64 {
327 10
328}
329
330fn default_loop_threshold() -> u32 {
331 3
332}
333
334fn default_rce_tools() -> Vec<String> {
335 vec![
336 "write_file".to_string(),
337 "create_file".to_string(),
338 "create_or_update_file".to_string(),
339 "python_repl".to_string(),
340 "bash".to_string(),
341 "execute_command".to_string(),
342 "run_script".to_string(),
343 "shell".to_string(),
344 ]
345}
346
347fn default_network_tools() -> Vec<String> {
348 vec![
349 "http_get".to_string(),
350 "http_post".to_string(),
351 "http_request".to_string(),
352 "curl".to_string(),
353 "fetch".to_string(),
354 "requests".to_string(),
355 "webhook".to_string(),
356 "send_email".to_string(),
357 ]
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_default_config() {
366 let config = AirlockConfig::default();
367 assert_eq!(config.mode, AirlockMode::Shadow);
368 assert!(config.rce.enabled);
369 assert!(config.velocity.enabled);
370 assert!(config.exfiltration.enabled);
371 }
372
373 #[test]
374 fn test_default_rce_tools() {
375 let tools = default_rce_tools();
376 assert!(tools.contains(&"write_file".to_string()));
377 assert!(tools.contains(&"bash".to_string()));
378 }
379
380 #[test]
381 fn test_velocity_defaults() {
382 let config = VelocityConfig::default();
383 assert_eq!(config.max_cost_cents, 100);
384 assert_eq!(config.window_seconds, 10);
385 assert_eq!(config.loop_threshold, 3);
386 }
387}