use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SensitivityLabel {
Public = 0,
Internal = 1,
Confidential = 2,
Untrusted = 3,
}
impl SensitivityLabel {
pub fn is_untrusted(&self) -> bool {
matches!(self, Self::Untrusted)
}
pub fn is_confidential(&self) -> bool {
matches!(self, Self::Confidential)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SinkCapability {
ReadOnlyInspection,
FileMutation,
PrivilegedExecution,
NetworkEgress,
}
#[derive(Debug, thiserror::Error)]
pub enum TaintViolation {
#[error("Taint Flow Violation: Untrusted data cannot flow into privileged sink '{0:?}' without sanitization or supervisor approval")]
UntrustedToPrivilegedSink(SinkCapability),
#[error("Confidentiality Flow Violation: Confidential data '{0}' cannot egress to external network destination")]
ConfidentialEgressBlocked(String),
}
pub struct TaintTracker {
item_labels: Arc<RwLock<HashMap<String, SensitivityLabel>>>,
violations: AtomicU64,
}
impl Default for TaintTracker {
fn default() -> Self {
Self::new()
}
}
impl TaintTracker {
pub fn new() -> Self {
Self {
item_labels: Arc::new(RwLock::new(HashMap::new())),
violations: AtomicU64::new(0),
}
}
pub fn tag_item(&self, item_id: &str, label: SensitivityLabel) {
self.item_labels.write().insert(item_id.to_string(), label);
}
pub fn get_label(&self, item_id: &str) -> SensitivityLabel {
self.item_labels
.read()
.get(item_id)
.copied()
.unwrap_or(SensitivityLabel::Public)
}
pub fn check_flow(
&self,
source_label: SensitivityLabel,
target_sink: SinkCapability,
) -> Result<(), TaintViolation> {
match (source_label, target_sink) {
(SensitivityLabel::Untrusted, SinkCapability::PrivilegedExecution) => {
self.violations.fetch_add(1, Ordering::Relaxed);
Err(TaintViolation::UntrustedToPrivilegedSink(target_sink))
}
(SensitivityLabel::Confidential, SinkCapability::NetworkEgress) => {
self.violations.fetch_add(1, Ordering::Relaxed);
Err(TaintViolation::ConfidentialEgressBlocked(
"Network egress destination rejected for confidential payload".into(),
))
}
_ => Ok(()),
}
}
pub fn scan_json_arguments(
&self,
args: &Value,
sink: SinkCapability,
) -> Result<(), TaintViolation> {
if let Some(obj) = args.as_object() {
if let Some(taint_val) = obj.get("_taint") {
if taint_val.as_str() == Some("untrusted") {
return self.check_flow(SensitivityLabel::Untrusted, sink);
}
}
}
Ok(())
}
pub fn total_violations(&self) -> u64 {
self.violations.load(Ordering::Relaxed)
}
}