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
170impl Default for SchemaDriftConfig {
171 fn default() -> Self {
172 Self {
173 enabled: true,
174 risk_score: default_schema_drift_risk_score(),
175 }
176 }
177}
178
179fn default_schema_drift_risk_score() -> u8 {
180 70
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct BehavioralDriftConfig {
186 #[serde(default = "default_true")]
188 pub enabled: bool,
189
190 #[serde(default = "default_behavioral_window")]
192 pub window_size: usize,
193
194 #[serde(default = "default_behavioral_min_observations")]
197 pub min_observations: usize,
198
199 #[serde(default = "default_behavioral_z_threshold")]
203 pub z_threshold: f32,
204
205 #[serde(default = "default_behavioral_risk_score")]
207 pub risk_score: u8,
208}
209
210impl Default for BehavioralDriftConfig {
211 fn default() -> Self {
212 Self {
213 enabled: true,
214 window_size: default_behavioral_window(),
215 min_observations: default_behavioral_min_observations(),
216 z_threshold: default_behavioral_z_threshold(),
217 risk_score: default_behavioral_risk_score(),
218 }
219 }
220}
221
222fn default_behavioral_window() -> usize {
223 100
224}
225
226fn default_behavioral_min_observations() -> usize {
227 20
228}
229
230fn default_behavioral_z_threshold() -> f32 {
231 3.0
232}
233
234fn default_behavioral_risk_score() -> u8 {
235 65
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct CoherenceConfig {
249 #[serde(default = "default_true")]
251 pub enabled: bool,
252
253 #[serde(default = "default_coherence_lookahead")]
257 pub lookahead: usize,
258
259 #[serde(default = "default_coherence_risk_score")]
262 pub risk_score: u8,
263
264 #[serde(default = "default_coherence_min_confidence")]
267 pub min_confidence: f64,
268}
269
270impl Default for CoherenceConfig {
271 fn default() -> Self {
272 Self {
273 enabled: true,
274 lookahead: default_coherence_lookahead(),
275 risk_score: default_coherence_risk_score(),
276 min_confidence: default_coherence_min_confidence(),
277 }
278 }
279}
280
281fn default_coherence_lookahead() -> usize {
282 8
283}
284
285fn default_coherence_risk_score() -> u8 {
286 70
287}
288
289fn default_coherence_min_confidence() -> f64 {
290 0.5
291}
292
293fn default_true() -> bool {
298 true
299}
300
301fn default_max_cost_cents() -> u64 {
302 100 }
304
305fn default_window_seconds() -> u64 {
306 10
307}
308
309fn default_loop_threshold() -> u32 {
310 3
311}
312
313fn default_rce_tools() -> Vec<String> {
314 vec![
315 "write_file".to_string(),
316 "create_file".to_string(),
317 "create_or_update_file".to_string(),
318 "python_repl".to_string(),
319 "bash".to_string(),
320 "execute_command".to_string(),
321 "run_script".to_string(),
322 "shell".to_string(),
323 ]
324}
325
326fn default_network_tools() -> Vec<String> {
327 vec![
328 "http_get".to_string(),
329 "http_post".to_string(),
330 "http_request".to_string(),
331 "curl".to_string(),
332 "fetch".to_string(),
333 "requests".to_string(),
334 "webhook".to_string(),
335 "send_email".to_string(),
336 ]
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn test_default_config() {
345 let config = AirlockConfig::default();
346 assert_eq!(config.mode, AirlockMode::Shadow);
347 assert!(config.rce.enabled);
348 assert!(config.velocity.enabled);
349 assert!(config.exfiltration.enabled);
350 }
351
352 #[test]
353 fn test_default_rce_tools() {
354 let tools = default_rce_tools();
355 assert!(tools.contains(&"write_file".to_string()));
356 assert!(tools.contains(&"bash".to_string()));
357 }
358
359 #[test]
360 fn test_velocity_defaults() {
361 let config = VelocityConfig::default();
362 assert_eq!(config.max_cost_cents, 100);
363 assert_eq!(config.window_seconds, 10);
364 assert_eq!(config.loop_threshold, 3);
365 }
366}