Documents
Alerting and Detection Engine
Alerting and Detection Engine
Type
Document
Status
Published
Created
Oct 31, 2025
Updated
Oct 31, 2025
Updated by
Dosu Bot

Alerting System Data Models#

Alert Struct#

The Alert struct models a single alert event generated by the detection engine. It includes the following fields (source):

pub struct Alert {
    pub id: Uuid,
    pub severity: AlertSeverity,
    pub title: String,
    pub description: String,
    pub detection_rule_id: String,
    pub process_record: ProcessRecord,
    pub timestamp: SystemTime,
    pub deduplication_key: String,
    pub context: AlertContext,
}

The struct derives Serialize and Deserialize via serde, supporting JSON serialization and deserialization. This enables easy transmission and storage of alert data.

AlertSeverity#

AlertSeverity is an enum representing the severity of an alert. Possible values are Low, Medium, High, and Critical. It supports string parsing (case-insensitive) and display as lowercase strings. Example:

let sev = "High".parse::<AlertSeverity>().unwrap();
assert_eq!(sev, AlertSeverity::High);
assert_eq!(sev.to_string(), "high");

AlertContext#

AlertContext provides additional context for an alert. It contains a HashMap<String, String> for arbitrary key-value data, a vector of tags, an optional source string, and an optional confidence value (a f64 between 0.0 and 1.0). Builder-style methods allow chaining:

let ctx = AlertContext::new()
    .with_data("user", "alice")
    .with_tag("suspicious")
    .with_source("kernel")
    .with_confidence(0.85).unwrap();

Alert Construction and Methods#

The Alert::new constructor generates a new UUID, sets the timestamp to the current system time, and constructs the deduplication key as <severity>:<detection_rule_id>:<title>. Methods are provided to add context data, tags, source, and confidence (with validation). Alerts can compute their age in seconds and determine if they are "recent" based on a configurable threshold (source).

Deduplication Keys#

The deduplication_key field uniquely identifies an alert to prevent duplicates. It is constructed as <severity>:<detection_rule_id>:<title> at alert creation. This key is used by downstream systems (such as alert sinks) to identify and suppress duplicate alerts within a configurable deduplication window (source).

Detection Engine#

Rule Identification and Matching#

Detection rules are modeled by the DetectionRule struct, which includes fields such as id, name, description, sql_query, severity, enabled, tags, and metadata. Each rule is uniquely identified by its id (source).

The detection engine (DetectionEngine) manages rules in a HashMap keyed by rule ID. Rules are loaded using load_rule, which validates the rule's SQL before insertion. Rules can be enabled or disabled, and removed by ID.

When executing, the engine iterates over all enabled rules and applies them to process data. The current implementation matches rules by their metadata.category field. For example, a rule with category suspicious_process generates an alert for any process whose name contains "suspicious"; a rule with category high_cpu generates an alert for processes with cpu_usage > 80.0. This logic is a placeholder for future SQL-based execution (source).

Rule Validation#

Rules are validated using the validate_sql method, which ensures that the SQL query is a single SELECT statement, does not use banned functions, and does not exceed limits on projections or joins. The is_valid method checks that the rule has a non-empty name and SQL query and passes SQL validation (source).

Configuration Options#

recent_threshold_seconds#

The AlertingConfig struct defines alerting configuration, including the recent_threshold_seconds field. This field specifies the threshold (in seconds) for considering an alert as "recent". The default is 3600 seconds (1 hour). This value is used by Alert::is_recent_with_config to determine alert recency (source).

The configuration system supports hierarchical overrides: system config file, user config file, environment variables, and embedded defaults. The environment variable {PREFIX}_RECENT_THRESHOLD_SECONDS can override the recent_threshold_seconds value.

Other relevant configuration fields include dedup_window_seconds (deduplication window) and max_alerts_per_minute (rate limiting).

Testing and Validation Practices#

The alerting system includes comprehensive unit tests for alert creation, serialization/deserialization, severity parsing and display, context manipulation, confidence validation, alert age, and recency checks with custom thresholds and configurations. Detection rules are tested for creation, serialization, SQL validation (including rejection of invalid or unsafe queries), and lifecycle operations (enable/disable, tagging, metadata). The detection engine is tested for rule loading, duplicate rule handling, invalid SQL rejection, rule removal, enabling/disabling, and rule execution scenarios (including multiple rules, disabled rules, and no matches) (alert tests, rule tests, engine tests, config tests).

Configuration loading and merging, including environment variable overrides, are also validated by tests to ensure correct application of settings and error handling for invalid configurations.

Example: Creating and Serializing an Alert#

let process = ProcessRecord::new(1234, "test-process".to_owned());
let alert = Alert::new(
    AlertSeverity::High,
    "Suspicious Process Detected",
    "A potentially malicious process was detected",
    "rule-001",
    process,
)
.with_tag("malware")
.with_source("kernel")
.with_confidence(0.95)
.expect("valid confidence");

let json = serde_json::to_string(&alert).unwrap();
let deserialized: Alert = serde_json::from_str(&json).unwrap();
assert_eq!(alert, deserialized);

Example: Checking Alert Recency with Configuration#

let alert = /* ... */;
let config = AlertingConfig::default();
if alert.is_recent_with_config(&config) {
    // Alert is recent (within config.recent_threshold_seconds)
}

Example: Detection Engine Rule Loading and Execution#

let mut engine = DetectionEngine::new();
let rule = DetectionRule::new(
    "rule-1",
    "Suspicious Process Rule",
    "Detects suspicious processes",
    "SELECT * FROM processes WHERE name LIKE '%suspicious%'",
    "suspicious_process",
    AlertSeverity::High,
);
engine.load_rule(rule).unwrap();

let processes = vec![ProcessRecord::new(1234, "suspicious-process".to_owned())];
let alerts = engine.execute_rules(&processes);
assert!(!alerts.is_empty());