Risk Scoring
For: Platform operators tuning behavioral anomaly detection on Axemere Gateway.
Operations Overview | Approval Workflows | Quarantine | Risk Scoring | CP Connectivity | Telemetry
Axemere Gateway computes a composite risk score (0.0–1.0) for each request before policy evaluation. The score is built from four behavioral signals, each weighted independently. Policy rules read the score and signals via the context.risk.* DSL namespace and can trigger any decision: allow, deny, quarantine, or require_approval.
Table of Contents
- How Risk Scoring Works
- Risk Signals
- Composite Score Calculation
- Baseline Warm-Up
- DSL Fields
- Policy Patterns
- Tuning Thresholds
- Configuration
- Prometheus Metrics
- Troubleshooting
- See Also
How Risk Scoring Works
Risk scoring runs on every request before policy evaluation. The score is computed in-process (no external call) and injected into the evaluation context. Policy rules read it from context.risk.score and context.risk.signals.
Risk scoring is per-org, per-workload: each workload maintains its own baseline counters. A spike on one workload does not affect the score for another.
Risk Signals
rate_spike
Weight: 0.40
Detects a sudden increase in request rate relative to the workload's recent baseline.
- Compares current requests-per-minute against a sliding baseline window.
- Fires when:
current_rate > baseline_rate × MVGC_RISK_RATE_THRESHOLD - Default threshold:
3.0(current rate 3× the baseline triggers full signal contribution)
Example: Baseline is 10 req/min. Current window hits 35 req/min → rate is 3.5× baseline → rate_spike fires.
cost_anomaly
Weight: 0.30
Detects abnormal cost accumulation rate relative to recent spend patterns.
- Compares current estimated cost-per-minute against a sliding baseline window.
- Fires when:
current_cost_rate > baseline_cost_rate × MVGC_RISK_COST_THRESHOLD - Default threshold:
3.0
Example: Baseline spend is $0.10/min. Current window is $0.45/min → cost_anomaly fires.
target_diversity
Weight: 0.15
Detects a sudden increase in the number of distinct target hosts contacted.
- Compares distinct target hosts in the recent window against the baseline window.
- Fires when the diversity ratio exceeds
MVGC_RISK_TARGET_DIVERSITY_THRESHOLD. - Default threshold:
2.0
Known design limitation: The 5-minute baseline window always includes the 1-minute recent window, making the diversity threshold difficult to exceed in practice. This signal is most useful for detecting sudden host fan-out (e.g. a compromised workload scanning many endpoints) rather than gradual drift.
time_of_day
Weight: 0.15
Detects requests outside configured business hours.
- Fires when the request timestamp falls outside
MVGC_RISK_BUSINESS_HOURS. - Default:
09:00-17:00(24-hour UTC). - Timezone is always UTC; configure accordingly for your region.
Example: A request arrives at 02:30 UTC → time_of_day fires → contributes 0.15 to the composite score.
Composite Score Calculation
Each active signal contributes its full weight to the composite score:
composite_score = Σ (signal_weight × signal_active ? 1.0 : 0.0)
| Signals active | Score |
|---|---|
| None | 0.00 |
time_of_day only | 0.15 |
target_diversity only | 0.15 |
time_of_day + target_diversity | 0.30 |
cost_anomaly only | 0.30 |
rate_spike only | 0.40 |
rate_spike + time_of_day | 0.55 |
rate_spike + cost_anomaly | 0.70 |
rate_spike + cost_anomaly + time_of_day | 0.85 |
| All four signals | 1.00 |
Typical thresholds used in policy:
> 0.7→require_approvalorrisk_alertSIEM event> 0.8→quarantine
Baseline Warm-Up
rate_spike and cost_anomaly require a minimum of 5 observations before they can fire. Until the baseline is warm:
- The signal is treated as inactive (contributes 0.0 to the score).
time_of_dayandtarget_diversityfire normally regardless of warm-up state.- This prevents false positives on fresh gateway restarts or brand-new workloads.
Baseline state is in-memory and resets on gateway restart. If restarts are frequent, consider increasing
MVGC_RISK_BASELINE_WINDOWor accepting thatrate_spike/cost_anomalywill be inactive for the first few requests after each restart.
DSL Fields
Once the risk scorer runs, these fields are available in all policy layers:
| DSL field | Type | Description |
|---|---|---|
context.risk.score | decimal string | Composite risk score, "0.00" to "1.00" |
context.risk.request_rate | string | Requests per minute in the current window |
context.risk.cost_rate | string | Estimated cost per minute in the current window |
context.risk.signals | comma-separated string | Active signal names, e.g. "rate_spike,time_of_day" |
Caller-supplied context fields (also in the context namespace):
| DSL field | Source | Description |
|---|---|---|
context.purpose | ActionRequest.context.purpose | Caller-declared intent string |
context.labels.<key> | ActionRequest.context.labels | Caller-supplied label (e.g. context.labels.env) |
Policy Patterns
Quarantine on high composite score:
risk: - id: risk.quarantine.high_score priority: 100 when: field: context.risk.score gt: "0.8" effect: decision: quarantine reason: "composite risk score exceeds threshold"
Require approval on moderate risk + rate spike:
risk: - id: risk.require_approval.moderate_with_spike priority: 90 when: all: - field: context.risk.score gt: "0.6" - field: context.risk.signals operator: in value: "rate_spike" effect: decision: require_approval reason: "elevated risk with rate spike — manual review"
Deny outside business hours for sensitive action types:
risk: - id: risk.deny.outside_hours.sensitive priority: 80 when: all: - field: context.risk.signals operator: in value: "time_of_day" - field: action.type operator: in value: "ai.infer" effect: decision: deny reason: "AI inference outside business hours not permitted"
Allow but require purpose declaration:
identity: - id: identity.deny.no_purpose priority: 100 when: field: context.purpose operator: exists negate: true effect: decision: deny reason: "requests must declare a purpose"
Tuning Thresholds
Start permissive and tighten based on observed baseline data from mvgc_risk_score metrics:
- Run the gateway for 24–48 hours with no
quarantineordenyrules on risk. - Observe the
mvgc_risk_scoregauge andmvgc_risk_signalscounter in Prometheus. - Identify the normal score range for your workloads.
- Set your policy threshold above the 99th percentile of normal scores.
- Start with
require_approvalrather thanquarantineto validate the threshold before auto-blocking.
Example tuning sequence:
Week 1: observe, no risk rules
Week 2: add require_approval at score > 0.7 — watch approval queue
Week 3: move to quarantine at score > 0.8, keep require_approval at 0.65–0.80
Configuration
Risk scoring has two independent configuration surfaces: detection thresholds (how sensitive each signal is) and policy score thresholds (what score level triggers a decision). These are configured differently depending on how your gateway is deployed.
Detection Thresholds
Detection thresholds control when each signal fires. Set them in mvgc.yaml under the risk: stanza, or via environment variable. Environment variables take precedence.
# mvgc.yaml risk: rate_threshold: 3.0 # rate_spike fires when req/min > N × baseline (default 3.0) cost_threshold: 3.0 # cost_anomaly fires when cost/min > N × baseline (default 3.0) window_size: 5m # sliding baseline window (default 5m) business_hours: "09:00-17:00" # UTC; outside this window fires time_of_day
target_diversity_threshold is env-var only (not available in the YAML stanza):
| Variable | Default | Description |
|---|---|---|
MVGC_RISK_RATE_THRESHOLD | 3.0 | Multiplier above baseline rate to fire rate_spike |
MVGC_RISK_COST_THRESHOLD | 3.0 | Multiplier above baseline cost rate to fire cost_anomaly |
MVGC_RISK_TARGET_DIVERSITY_THRESHOLD | 2.0 | Multiplier above baseline distinct hosts to fire target_diversity |
MVGC_RISK_BUSINESS_HOURS | 09:00-17:00 | UTC time range considered normal; requests outside fire time_of_day |
MVGC_RISK_BASELINE_WINDOW | 5m | Sliding window duration for baseline calculations |
Policy Score Thresholds
Policy score thresholds determine what composite score triggers a quarantine, approval, or deny decision. These live in policy rules, not in gateway config.
CP-attached gateways: Adjust score thresholds in the policy rule via the console. Changes are pushed as a signed bundle and take effect on the next gateway check-in.
Free Gateway (no CP): Edit the risk rule directly in your local policy YAML file (default directory: configs/policies/):
risk: - id: risk.quarantine.high_score priority: 100 when: field: context.risk.score gt: "0.8" # raise this value to reduce quarantine frequency effect: decision: quarantine reason: "composite risk score exceeds threshold"
After editing a local policy file, restart the gateway or wait for the policy cache TTL to expire (default: 5 minutes, configurable via MVGC_POLICY_CACHE_TTL or policies.cache_ttl in mvgc.yaml).
Prometheus Metrics
| Metric | Type | Labels | Description |
|---|---|---|---|
mvgc_risk_score | Gauge | org_id | Last computed composite risk score per org |
mvgc_risk_signals | Counter | signal_type, org_id | Count of each active signal type per org |
Use mvgc_risk_score to build a dashboard panel showing risk score over time per org. Alert when the gauge stays above 0.5 for more than 5 minutes.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Risk score always 0.0 | Baseline not warm yet (< 5 observations) or all thresholds very high | Check observation count; lower thresholds to confirm scoring is working |
rate_spike never fires | Threshold too high for actual traffic patterns | Reduce MVGC_RISK_RATE_THRESHOLD; check mvgc_risk_signals for any activity |
time_of_day firing during expected hours | MVGC_RISK_BUSINESS_HOURS not in UTC | Convert your business hours to UTC when setting the env var |
| Workload quarantined on every restart | Baseline resets at restart; first requests hit empty baseline | Increase threshold or add a warmup rule that bypasses risk for the first N minutes after restart |
target_diversity never fires | Baseline window subsumes recent window, by design | Use this signal only for extreme fan-out (10+ new hosts); lower threshold to 1.5 for more sensitivity |
See Also
- Quarantine — auto-blocking high-risk requests
- Approval Workflows — holding requests for review
- Telemetry —
mvgc_risk_scoreandmvgc_risk_signalsmetrics - Network Operations Guide — risk DSL reference
- Glossary — risk_score
- Glossary — risk_signals