Entity risk score¶
LogMan.io uses two related risk score concepts:
| Concept | Where you see it | Meaning |
|---|---|---|
| Detection risk score | Complex events, alerts, Discover | Score of one detection after correlation rules and lookups |
| Entity risk score | Assets list and asset detail | How risky this user, host, or service is right now, based on recent detections |
Detection scoring is described in Risk scoring in Correlator. This page explains the entity risk score on assets.
Where to look in the UI¶
| Place | What you see | What it tells you |
|---|---|---|
| Assets table | Risk score column | Compare principals at a glance; sort by risk |
| Asset detail sidebar | Average risk score | Current entity score after decay |
| Asset detail chart | Risk score line | Peak detection scores over time (not the same number as the sidebar) |
SOC triage on Monday morning
Open Assets and sort by risk score descending. A workstation at 72 after several failed logins over the weekend stays near the top even if the last alert was hours ago. A server that had one 85 alert yesterday but nothing since may already show 40, because the score fades when activity stops.
One loud alert vs many quiet ones
User jsmith triggers one rule with detection risk 80. Their entity score moves toward 80. The next day, three minor detections at 20 each arrive: the entity score does not become 140. It blends toward the new values (roughly 40 if they arrived close together). Old noise does not stack without limit.
Sidebar vs chart
The sidebar Average risk score answers: "Should I worry about this principal now?"
The risk score chart answers: "When did detections spike?"
After a quiet week the sidebar may read 0 or be empty, while the chart still shows a spike from last Tuesday.
How the entity score is built¶
LogMan.io Assets processes correlation detections for each tenant. When a detection carries event.risk_score and identifies a host, user, or service, that value updates the entity score for that principal.
The score is a time decayed weighted average of recent detection values (UEBA style), not a running sum. See Risk score decay (detailed) for the exact model.
At a high level:
- New detections pull the score up (or down if the new value is lower).
- Time without detections pulls the score down (default half life 24 hours).
- Many weak alerts do not add up forever the way a raw sum would.
Example (simplified): The entity score was 80 from one alert. A new detection with risk 20 arrives immediately: the blended score becomes about 50, not 100. After 24 hours with no further detections, the score halves again toward 25, then new activity can move it again.
Why an average, not a sum?
Ten low priority alerts spread across a week should not push a user to the same level as ten critical incidents. The entity score reflects recent relevance, similar to UEBA products where stale risk fades instead of living forever in a running total.
Risk score decay (detailed)¶
This section describes the exact entity risk model used by lmio-assets when merging complex lane detections.
Input¶
Only complex lane events matter. For each detection, the service reads:
| Input | Source |
|---|---|
| Principal | Host, user, or service id from the tenant schema (same fields as activity assets) |
Contribution c |
event.risk_score on the detection (must be > 0 to change state) |
Event time t |
Principal datetime on the complex event (typically @timestamp), as Unix milliseconds |
Detections with c ≤ 0 are ignored for scoring.
Internal state in MongoDB¶
The integer risk_score shown in the UI is derived from three internal fields on the asset document:
| Field | Role |
|---|---|
risk_score_sum (S) |
Weighted sum of past detection values |
risk_score_weight (W) |
Sum of weights (each detection adds 1 after decay) |
risk_score_at |
Unix ms when the accumulators were last advanced to event time |
Entity score at any instant:
score = S / W (rounded to integer for API / list sort)
API responses and exports do not expose risk_score_sum, risk_score_weight, or risk_score_at; only the computed risk_score.
Decay between detections¶
When time advances from the previous update (risk_score_at) to a new event time t without blending a new detection yet, both accumulators are multiplied by the same factor:
decay(Δt) = 0.5 ^ (Δt_hours / half_life_hours)
S ← S × decay(Δt)
W ← W × decay(Δt)
Default half_life_hours = 24 ([assets] risk_score_decay_half_life, default 24h).
After one full half life with no new detections, both S and W shrink by half, so score = S/W stays the same until a new detection is merged.
Intuition: the half life does not lower the displayed score while nothing new happens (S and W shrink equally, so S/W stays constant). It controls how much older detections count when the next detection is merged — after 24 h idle, the previous history contributes at half weight before the new value is blended in.
Idle vs. new detections
No new complex events: sidebar and list risk_score stay at the last value (e.g. 80 remains 80).
New detection after idle time: history is decayed first, then averaged with the new event.risk_score — that is when the number moves down (or up).
Analyst tips that describe a score “fading” over a quiet weekend assume additional lower detections or a mix of past samples, not idle decay alone from one old alert.
Merging a new detection¶
When a detection with value c > 0 arrives at time t:
- If
t ≥ risk_score_at: apply decay fromrisk_score_attot, then setrisk_score_at = t. - If
t < risk_score_at(late event): addcto S and +1 to W, but do not moverisk_score_atbackward. - Update accumulators:
S ← S × decay(Δt) + c (Δt = 0 when late event)
W ← W × decay(Δt) + 1
score = round(S / W)
Buffered detections from Kafka are merged in event time order per asset before write.
Clearing low scores¶
If after decay or merge score < risk_score_decay_clear_below (default 0.5), all risk fields are removed from Mongo (risk_score, risk_score_sum, risk_score_weight). The asset then shows no entity risk in the UI until the next qualifying detection.
When decay is applied¶
| Moment | Behavior |
|---|---|
| Complex lane flush | Detections merged into Mongo; accumulators advanced to the latest event time in the batch |
| HTTP GET (list, detail, export) | risk_score recomputed at now from stored S, W, and risk_score_at (passive decay) |
| Background tick | Every 10 minutes (Application.tick/600!), stored S, W, and risk_score are persisted after passive decay so sort by risk score in the asset list stays aligned without reading every row on each request |
Between ticks, list/detail still show the correct decayed value at read time even if Mongo still holds slightly stale integers.
Multi-instance safety¶
Several lmio-assets instances may consume the same complex topic. Merges use compare-and-set on risk_score_at: an update applies only if risk_score_at in Mongo still matches the value read. Conflicting writes retry (default 8 attempts, [assets] risk_score_cas_retries). The passive decay job uses the same guard so a concurrent detection merge is not overwritten.
Numeric example¶
Configuration: half life 24 h, clear_below = 0.5.
- T0: First detection
c = 80→ S = 80, W = 1, score = 80. - T0 + 1 h: Second detection
c = 20→ decay(1 h) ≈ 0.971, S ≈ 80×0.971 + 20 ≈ 97.7, W ≈ 1.971, score ≈ 50. - T0 + 25 h (no further detections): if still idle, score remains ≈ 50 (S and W decay equally).
- T0 + 25 h with a new detection
c = 10: history at half weight blends toward 10 — the displayed score drops because a new sample arrived, not from idle time alone.
Sidebar vs risk score chart¶
These views answer different questions and use different data:
| View | Data | Question |
|---|---|---|
| Average risk score (sidebar, list column) | Decayed entity model above | How risky is this principal right now? |
| Risk score chart (asset detail) | max(event.risk_score) per time bucket on complex lane events only |
When did individual detections peak in each interval? |
The chart can show a spike from last Tuesday while the sidebar is already 0 after a quiet week.
Optional risk score weight¶
On the asset detail page you can set Risk score weight (0 to 100). Use this when your deployment tunes how strongly future detections involving that principal should score (for example a domain controller or a known service account).
This override is separate from Average risk score in the sidebar, which still comes from real detections on that asset.
You need permission to edit lookups. Use Clear weight to remove the override.
Score over time¶
- Detections use the event time on the complex event (typically
@timestamp). - Opening the asset list or detail shows the score as it is now, including passive decay since the last detection (see Risk score decay (detailed)).
- The service also refreshes stored scores every 10 minutes in the background so sorting in the asset list stays aligned with the model.
- Very low scores (below 0.5 by default) disappear from the UI.
If a detection arrives late (event time older than the last update), its value still contributes to the average, but decay does not move backward in time.
Configuration (administrators)¶
[assets] key |
Default | Description |
|---|---|---|
risk_score_decay_half_life |
24h |
Half life when down-weighting past detections before merging a new one |
risk_score_decay_clear_below |
0.5 |
Hide and clear scores below this value |
risk_score_cas_retries |
8 |
Optimistic concurrency retries when merging detections across instances |
complex_mongo_batch_max |
100 |
Buffered detections before persist |
complex_mongo_batch_max_age |
60 |
Max seconds before partial buffer flush |
See Configuration for service setup and dependencies.
Related documentation¶
- Risk scoring in Correlator: per detection
event.risk_score - LogMan.io Assets overview