SentrySentry
AI

Local ONNX Model

Classic ThreatModel (features → sklearn → ONNX) via ort, with hot-swap retraining

Local ONNX Model

Sentry's local model is a classic-ML classifier (logistic regression) that runs as an async fork beside the hot path. Inference is local — no network, deterministic, typically sub-ms — via ort (ONNX Runtime), behind the optional onnx Cargo feature (disabled by default).

The ThreatModel trait

/// A local, deterministic, no-network threat model.
#[async_trait]
pub trait ThreatModel: Send + Sync {
    fn name(&self) -> &'static str;
    fn version(&self) -> &str;
    async fn analyze(&self, evt: &Event) -> anyhow::Result<Vec<Signal>>;
}

Defined in sentry-ai/src/threat.rs — the pipeline depends on a Box<dyn ThreatModel> without pulling ort into every build. The ONNX implementation is OnnxThreatModel (onnx feature), loaded by the daemon in build_ai_fork when [ai] enabled = true. Without the feature, the stage stays disabled with a warning — the rest of Sentry keeps working.

load validates the model's input width against FEATURE_NAMES: a model trained against a different feature version fails at startup with retraining instructions — it never runs out of sync. Anomaly probability above threshold emits an AnomalousPayload signal weighted signal_weight.

Features — the single source of truth

sentry-ai/src/features.rs is the single source of truth for the model input: the same extract function runs at inference time (daemon fork) and at training export time (sentry model export writes a CSV whose header is FEATURE_NAMES). The training script validates the CSV header and fails loudly if the two sides drift — training and inference can never diverge.

There are 25 features, all normalized to 0.0–1.0 (fixed-length vector; non-HTTP events extract an all-zero vector). Summary by group:

GroupFeatures
Path structurepath_len, query_len, path_depth, path_digit_ratio, path_special_ratio, path_entropy, path_encoded_count, path_dot_dot, path_null_byte
Attack tokens (over URL-decoded, lowercased text)sqli_token_score, xss_token_score, traversal_token_score, jndi_token, cmd_token_score
File extensionhas_file_ext, ext_is_script, ext_is_sensitive
HTTP methodmethod_is_get, method_is_post
User-Agentua_len, ua_is_empty, ua_bot_token
Response statusstatus_4xx, status_5xx
Query stringquery_param_count

Retraining loop

Feature extraction happens exclusively in Rust — the Python script only consumes exported vectors. The full cycle:

  1. Export — sentry model export (handler, flags) writes a CSV (FEATURE_NAMES,label) with features extracted by Rust over recent events from Postgres:
    • --hours N — look-back window (default 720 = 30 days);
    • --out <file> — destination (default dataset.csv);
    • --synthetic — synthetic seed dataset without Postgres (--rows, default 4000, half benign);
    • --confirmed — labels from confirmed incidents (incident-linked event = 1, everything else = 0). Without the flag, positives are events with verdict block/challenge/rate_limit.
  2. Train — tools/train_model.py (tools/train_model.py): StandardScaler + LogisticRegression (sklearn, class_weight="balanced") evaluated on a holdout split, exported to ONNX with zipmap: false (probabilities as a plain float tensor).
  3. Replace — swap the file pointed to by model_path (the committed seed is models/anomaly_v1.onnx).
  4. Hot swap — sentry model reload (handler) sends NOTIFY sentry_model_changed through Postgres; the daemon reloads the file from disk and swaps the model hot (listener), with the live model in Arc<RwLock<Arc<dyn ThreatModel>>>. A failed load keeps the previous model.
  5. Status — sentry model status (handler) shows enabled, path, mode/trigger/threshold, whether the file loads (with --features onnx) and the active LLM provider.
sentry model export --synthetic
python tools/train_model.py --csv dataset.csv --out models/anomaly_v1.onnx
sentry model reload    # requires Postgres: the daemon listens on NOTIFY sentry_model_changed
sentry model status

The build needs the feature: cargo build --release --features sentry-cli/onnx.

[ai] configuration

[ai]
enabled = false
model_path = "models/anomaly_v1.onnx"
threshold = 0.7             # anomaly probability that emits the signal
signal_weight = 25          # AnomalousPayload weight (override: [scorer.weights])
mode = "fork"               # fork | inline | shadow
trigger = "above_score"     # above_score | always | quarantine_only
min_score = 20              # hot-path score gate (above_score)
concurrency = 4             # max parallel inferences (fork mode)
cache_ttl_secs = 300        # result cache keyed by payload hash

Keys and defaults in AiConfig. What mode/trigger/cache do at runtime is described in AI Layer.

On this page