use crate::environment::{Confidence, EnvironmentSignal, ThreatLevel};
use security_core::classification::DataClassification;
use security_core::severity::SecuritySeverity;
use security_events::event::{EventOutcome, EventValue, SecurityEvent};
use security_events::kind::EventKind;
use security_events::sink::SecuritySink;
use serde::Serialize;
use std::sync::atomic::{AtomicU32, Ordering};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub enum ResponseAction {
Allow,
Warn,
Block,
Degrade,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub enum RaspDecision {
Allow,
Warn {
signal_category: String,
},
Block {
signal_category: String,
},
Degrade {
capabilities_removed: Vec<String>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct RaspPolicy {
pub root_response: ResponseAction,
pub emulator_response: ResponseAction,
pub debugger_response: ResponseAction,
pub unknown_response: ResponseAction,
}
impl Default for RaspPolicy {
fn default() -> Self {
Self {
root_response: ResponseAction::Warn,
emulator_response: ResponseAction::Warn,
debugger_response: ResponseAction::Warn,
unknown_response: ResponseAction::Allow,
}
}
}
impl RaspPolicy {
pub fn permissive() -> Self {
Self {
root_response: ResponseAction::Allow,
emulator_response: ResponseAction::Allow,
debugger_response: ResponseAction::Allow,
unknown_response: ResponseAction::Allow,
}
}
fn action_for(&self, signal: &EnvironmentSignal) -> ResponseAction {
match signal {
EnvironmentSignal::RootDetected { .. } => self.root_response,
EnvironmentSignal::EmulatorDetected { .. } => self.emulator_response,
EnvironmentSignal::DebuggerAttached { .. } => self.debugger_response,
EnvironmentSignal::Unknown { .. } => self.unknown_response,
}
}
}
pub struct RaspEngine {
policy: RaspPolicy,
threat_score: AtomicU32,
}
impl RaspEngine {
pub fn new(policy: RaspPolicy) -> Self {
Self {
policy,
threat_score: AtomicU32::new(0),
}
}
pub fn process_signal(
&self,
signal: &EnvironmentSignal,
sink: &impl SecuritySink,
) -> RaspDecision {
let weight = self.compute_weighted_score(signal);
if weight > 0 {
self.threat_score.fetch_add(weight, Ordering::Relaxed);
}
let severity = self.severity_for(signal);
let action = self.policy.action_for(signal);
let outcome = match action {
ResponseAction::Allow => EventOutcome::Success,
ResponseAction::Warn => EventOutcome::Success,
ResponseAction::Block => EventOutcome::Blocked,
ResponseAction::Degrade => EventOutcome::Success,
};
let mut event = SecurityEvent::new(EventKind::EnvironmentThreat, severity, outcome);
event.resource = Some(signal.category().to_string());
event.labels.insert(
"evidence".to_string(),
EventValue::Classified {
value: signal.evidence().to_string(),
classification: DataClassification::Internal,
},
);
if let Some(confidence) = signal.confidence() {
event.labels.insert(
"confidence".to_string(),
EventValue::Classified {
value: format!("{confidence:?}"),
classification: DataClassification::Internal,
},
);
}
sink.write_event(&event);
self.action_to_decision(action, signal)
}
pub fn threat_level(&self) -> ThreatLevel {
ThreatLevel::from_score(self.threat_score.load(Ordering::Relaxed))
}
fn compute_weighted_score(&self, signal: &EnvironmentSignal) -> u32 {
let base = signal.base_threat_weight();
let multiplier = match signal.confidence() {
Some(Confidence::High) => 100,
Some(Confidence::Medium) => 60,
Some(Confidence::Low) => 30,
None => 0,
};
base * multiplier / 100
}
fn severity_for(&self, signal: &EnvironmentSignal) -> SecuritySeverity {
match signal {
EnvironmentSignal::DebuggerAttached {
confidence: Confidence::High,
..
} => SecuritySeverity::Critical,
EnvironmentSignal::DebuggerAttached { .. } => SecuritySeverity::High,
EnvironmentSignal::RootDetected {
confidence: Confidence::High,
..
} => SecuritySeverity::High,
EnvironmentSignal::RootDetected { .. } => SecuritySeverity::Medium,
EnvironmentSignal::EmulatorDetected { .. } => SecuritySeverity::Medium,
EnvironmentSignal::Unknown { .. } => SecuritySeverity::Low,
}
}
fn action_to_decision(
&self,
action: ResponseAction,
signal: &EnvironmentSignal,
) -> RaspDecision {
match action {
ResponseAction::Allow => RaspDecision::Allow,
ResponseAction::Warn => RaspDecision::Warn {
signal_category: signal.category().to_string(),
},
ResponseAction::Block => RaspDecision::Block {
signal_category: signal.category().to_string(),
},
ResponseAction::Degrade => RaspDecision::Degrade {
capabilities_removed: self.degraded_capabilities(signal),
},
}
}
fn degraded_capabilities(&self, signal: &EnvironmentSignal) -> Vec<String> {
match signal {
EnvironmentSignal::RootDetected { .. } => {
vec!["secure_storage".to_string(), "biometric_auth".to_string()]
}
EnvironmentSignal::EmulatorDetected { .. } => vec![
"payment_processing".to_string(),
"sensitive_data_display".to_string(),
],
EnvironmentSignal::DebuggerAttached { .. } => {
vec!["all_sensitive_operations".to_string()]
}
EnvironmentSignal::Unknown { .. } => vec!["unknown_capability".to_string()],
}
}
}