Skip to content

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:

  1. If t ≥ risk_score_at: apply decay from risk_score_at to t, then set risk_score_at = t.
  2. If t < risk_score_at (late event): add c to S and +1 to W, but do not move risk_score_at backward.
  3. 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.

  1. T0: First detection c = 80 → S = 80, W = 1, score = 80.
  2. T0 + 1 h: Second detection c = 20 → decay(1 h) ≈ 0.971, S ≈ 80×0.971 + 20 ≈ 97.7, W ≈ 1.971, score ≈ 50.
  3. T0 + 25 h (no further detections): if still idle, score remains ≈ 50 (S and W decay equally).
  4. 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.

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.