Files
foxhunt/crates/ml-security/src/lib.rs
jgrusewski 4676fe79e2 refactor(ml): extract observability, stress-testing, security into sub-crates
- ml-observability (1.2K lines): alerts, dashboards, metrics modules.
  Depends on ml-core + common (ModelType). 4 tests passing.

- ml-stress-testing (1.3K lines): load_generator, market_simulator,
  performance_analyzer modules. Depends on ml-core + common + config.
  5 tests passing.

- ml-security (1.4K lines): anomaly_detector, prediction_validator
  modules. Depends on ml-core + ml-ensemble (EnsembleDecision,
  ModelVote, TradingAction). 17 tests passing.

Total: 18 sub-crates extracted from ml monolith.
Workspace: 0 errors, ml tests 876 + 26 in new sub-crates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00

228 lines
6.7 KiB
Rust

#![deny(clippy::unwrap_used, clippy::expect_used)]
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
#![allow(dead_code)]
#![allow(missing_docs)]
#![allow(missing_debug_implementations)]
#![allow(unused_crate_dependencies)]
#![allow(clippy::float_arithmetic)]
#![allow(clippy::non_ascii_literal)]
#![allow(clippy::str_to_string)]
#![allow(clippy::partial_pub_fields)]
#![allow(clippy::multiple_inherent_impl)]
#![allow(clippy::same_name_method)]
#![allow(clippy::shadow_reuse)]
#![allow(clippy::shadow_unrelated)]
#![allow(clippy::shadow_same)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::indexing_slicing)]
#![allow(clippy::missing_const_for_fn)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::integer_division)]
#![allow(clippy::cognitive_complexity)]
#![allow(clippy::similar_names)]
#![allow(clippy::clone_on_ref_ptr)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::as_conversions)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::default_numeric_fallback)]
#![allow(clippy::arithmetic_side_effects)]
#![allow(clippy::needless_range_loop)]
#![allow(clippy::into_iter_on_ref)]
#![allow(clippy::new_without_default)]
#![allow(clippy::manual_let_else)]
#![allow(clippy::unnecessary_wraps)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_lossless)]
#![allow(clippy::unused_async)]
#![allow(clippy::match_same_arms)]
#![allow(clippy::unused_self)]
#![allow(clippy::map_err_ignore)]
#![allow(clippy::single_match_else)]
#![allow(clippy::wildcard_imports)]
#![allow(clippy::unnecessary_cast)]
//! ML Security Module
//!
//! Provides comprehensive security features for ML inference:
//! - Prediction validation and model poisoning detection
//! - Ensemble anomaly detection
//! - Security event logging
//!
//! # Architecture
//!
//! ```text
//! ┌──────────────────┐ ┌──────────────────┐
//! │ Prediction │ │ Ensemble │
//! │ Validator │──────│ Anomaly │
//! │ │ │ Detector │
//! └────────┬─────────┘ └────────┬─────────┘
//! │ │
//! └─────────┬───────────────┘
//! │
//! ▼
//! ┌──────────────────┐
//! │ Security Event │
//! │ Logger │
//! └──────────────────┘
//! ```
pub mod anomaly_detector;
pub mod prediction_validator;
pub use anomaly_detector::{
Anomaly, AnomalyDetectorConfig, AnomalyReport, AnomalySeverity, DetectorStatistics,
EnsembleAnomalyDetector,
};
pub use prediction_validator::{
PredictionStats, PredictionValidator, ValidatedPrediction, ValidationConfig, ValidationFlag,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
/// Security event types for ML system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SecurityEventType {
// Checkpoint security
CheckpointSignatureFailure,
CheckpointSignatureMissing,
CheckpointTamperingDetected,
// Prediction validation
PredictionOutlierDetected,
PredictionOutOfBounds,
ExtremeRateExceeded,
// Ensemble anomalies
EnsembleSuddenShift,
CoordinatedAttackSuspected,
ModelBehavioralDrift,
// System events
AutomaticRollback,
ManualIntervention,
}
/// Security event for logging
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityEvent {
/// Event type
pub event_type: SecurityEventType,
/// Severity level
pub severity: SecuritySeverity,
/// Event timestamp
pub timestamp: DateTime<Utc>,
/// Model ID (if applicable)
pub model_id: Option<String>,
/// Checkpoint ID (if applicable)
pub checkpoint_id: Option<String>,
/// Prediction ID (if applicable)
pub prediction_id: Option<String>,
/// Human-readable description
pub description: String,
/// Additional metadata
pub metadata: serde_json::Value,
/// Action taken in response
pub action_taken: Option<String>,
}
/// Security severity levels
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum SecuritySeverity {
Low,
Medium,
High,
Critical,
}
impl SecurityEvent {
/// Create a new security event
pub fn new(
event_type: SecurityEventType,
severity: SecuritySeverity,
description: String,
) -> Self {
Self {
event_type,
severity,
timestamp: Utc::now(),
model_id: None,
checkpoint_id: None,
prediction_id: None,
description,
metadata: serde_json::Value::Null,
action_taken: None,
}
}
/// Set model ID
pub fn with_model_id(mut self, model_id: String) -> Self {
self.model_id = Some(model_id);
self
}
/// Set checkpoint ID
pub fn with_checkpoint_id(mut self, checkpoint_id: String) -> Self {
self.checkpoint_id = Some(checkpoint_id);
self
}
/// Set prediction ID
pub fn with_prediction_id(mut self, prediction_id: String) -> Self {
self.prediction_id = Some(prediction_id);
self
}
/// Set metadata
pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = metadata;
self
}
/// Set action taken
pub fn with_action(mut self, action: String) -> Self {
self.action_taken = Some(action);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_security_event_builder() {
let event = SecurityEvent::new(
SecurityEventType::PredictionOutlierDetected,
SecuritySeverity::Medium,
"Test outlier detection".to_owned(),
)
.with_model_id("DQN".to_owned())
.with_action("Flagged for review".to_owned());
assert_eq!(event.severity, SecuritySeverity::Medium);
assert_eq!(event.model_id, Some("DQN".to_owned()));
assert_eq!(event.action_taken, Some("Flagged for review".to_owned()));
}
#[test]
fn test_severity_ordering() {
assert!(SecuritySeverity::Critical > SecuritySeverity::High);
assert!(SecuritySeverity::High > SecuritySeverity::Medium);
assert!(SecuritySeverity::Medium > SecuritySeverity::Low);
}
}