use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum AirlockMode {
Enforce,
#[default]
Shadow,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AirlockConfig {
#[serde(default)]
pub mode: AirlockMode,
#[serde(default)]
pub rce: RceConfig,
#[serde(default)]
pub velocity: VelocityConfig,
#[serde(default)]
pub exfiltration: ExfiltrationConfig,
#[serde(default)]
pub schema_drift: SchemaDriftConfig,
#[serde(default)]
pub behavioral_drift: BehavioralDriftConfig,
}
impl Default for AirlockConfig {
fn default() -> Self {
Self {
mode: AirlockMode::Shadow,
rce: RceConfig::default(),
velocity: VelocityConfig::default(),
exfiltration: ExfiltrationConfig::default(),
schema_drift: SchemaDriftConfig::default(),
behavioral_drift: BehavioralDriftConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RceConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_rce_tools")]
pub target_tools: Vec<String>,
#[serde(default)]
pub custom_patterns: Vec<String>,
}
impl Default for RceConfig {
fn default() -> Self {
Self {
enabled: true,
target_tools: default_rce_tools(),
custom_patterns: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VelocityConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_max_cost_cents")]
pub max_cost_cents: u64,
#[serde(default = "default_window_seconds")]
pub window_seconds: u64,
#[serde(default = "default_loop_threshold")]
pub loop_threshold: u32,
}
impl Default for VelocityConfig {
fn default() -> Self {
Self {
enabled: true,
max_cost_cents: default_max_cost_cents(),
window_seconds: default_window_seconds(),
loop_threshold: default_loop_threshold(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExfiltrationConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_network_tools")]
pub target_tools: Vec<String>,
#[serde(default)]
pub allowed_domains: Vec<String>,
#[serde(default = "default_true")]
pub block_ip_addresses: bool,
#[serde(default = "default_true")]
pub credential_dlp_enabled: bool,
#[serde(default)]
pub data_budget_per_domain_bytes: Option<u64>,
}
impl Default for ExfiltrationConfig {
fn default() -> Self {
Self {
enabled: true,
target_tools: default_network_tools(),
allowed_domains: Vec::new(),
block_ip_addresses: true,
credential_dlp_enabled: true,
data_budget_per_domain_bytes: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchemaDriftConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_schema_drift_risk_score")]
pub risk_score: u8,
#[serde(default = "default_schema_drift_fail_closed")]
pub fail_closed_on_unregistered: bool,
}
impl Default for SchemaDriftConfig {
fn default() -> Self {
Self {
enabled: true,
risk_score: default_schema_drift_risk_score(),
fail_closed_on_unregistered: default_schema_drift_fail_closed(),
}
}
}
fn default_schema_drift_risk_score() -> u8 {
70
}
fn default_schema_drift_fail_closed() -> bool {
false
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BehavioralDriftConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_behavioral_window")]
pub window_size: usize,
#[serde(default = "default_behavioral_min_observations")]
pub min_observations: usize,
#[serde(default = "default_behavioral_z_threshold")]
pub z_threshold: f32,
#[serde(default = "default_behavioral_risk_score")]
pub risk_score: u8,
}
impl Default for BehavioralDriftConfig {
fn default() -> Self {
Self {
enabled: true,
window_size: default_behavioral_window(),
min_observations: default_behavioral_min_observations(),
z_threshold: default_behavioral_z_threshold(),
risk_score: default_behavioral_risk_score(),
}
}
}
fn default_behavioral_window() -> usize {
100
}
fn default_behavioral_min_observations() -> usize {
20
}
fn default_behavioral_z_threshold() -> f32 {
3.0
}
fn default_behavioral_risk_score() -> u8 {
65
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoherenceConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_coherence_lookahead")]
pub lookahead: usize,
#[serde(default = "default_coherence_risk_score")]
pub risk_score: u8,
#[serde(default = "default_coherence_min_confidence")]
pub min_confidence: f64,
}
impl Default for CoherenceConfig {
fn default() -> Self {
Self {
enabled: true,
lookahead: default_coherence_lookahead(),
risk_score: default_coherence_risk_score(),
min_confidence: default_coherence_min_confidence(),
}
}
}
fn default_coherence_lookahead() -> usize {
8
}
fn default_coherence_risk_score() -> u8 {
70
}
fn default_coherence_min_confidence() -> f64 {
0.5
}
fn default_true() -> bool {
true
}
fn default_max_cost_cents() -> u64 {
100 }
fn default_window_seconds() -> u64 {
10
}
fn default_loop_threshold() -> u32 {
3
}
fn default_rce_tools() -> Vec<String> {
vec![
"write_file".to_string(),
"create_file".to_string(),
"create_or_update_file".to_string(),
"python_repl".to_string(),
"bash".to_string(),
"execute_command".to_string(),
"run_script".to_string(),
"shell".to_string(),
]
}
fn default_network_tools() -> Vec<String> {
vec![
"http_get".to_string(),
"http_post".to_string(),
"http_request".to_string(),
"curl".to_string(),
"fetch".to_string(),
"requests".to_string(),
"webhook".to_string(),
"send_email".to_string(),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = AirlockConfig::default();
assert_eq!(config.mode, AirlockMode::Shadow);
assert!(config.rce.enabled);
assert!(config.velocity.enabled);
assert!(config.exfiltration.enabled);
}
#[test]
fn test_default_rce_tools() {
let tools = default_rce_tools();
assert!(tools.contains(&"write_file".to_string()));
assert!(tools.contains(&"bash".to_string()));
}
#[test]
fn test_velocity_defaults() {
let config = VelocityConfig::default();
assert_eq!(config.max_cost_cents, 100);
assert_eq!(config.window_seconds, 10);
assert_eq!(config.loop_threshold, 3);
}
}