Commit bfb1b79
[Entity Analytics] Anomaly detection behavior maintainer (elastic#269309)
## Summary
> [!NOTE]
> This only contains the server side changes for the entity behavior
feature. UI changes to come in subsequent PRs.
Introduces a new entity maintainer (`ml-anomaly-detection-jobs`) that
maintains the `entity.behaviors.anomaly_job_ids` field for an entity in
the entity store. This maintainer runs every 24 hours and looks back 90
days in order to capture all of the anomalous behavior for an entity in
the last 90 days.
During each run, the maintainer:
- Iterates over user and host entities from the entity store in batches
- For each batch, fetches anomaly records from security ML jobs for the
last 90 days and above the configured threshold minimum
- If anomaly records exist for an entity, its entity store entry is
updated to include the anomaly job ID.
- If anomaly records exist for an entity, additional supporting details
will be queried and stored in a details datastream
`.entity_analytics.ml-ad-jobs-latest-${namespace}`
The additional details that are fetched are job dependent:
For jobs that use the `rare` function (for example, rare country login),
only the anomalous value is stored in the anomaly record (for example,
`Iran`). In order to determine the baseline behavior for the entity, we
use the ML job configuration to aggregate against the source index (for
example, an aggregation to determine where an entity commonly logs in)
For other job types that are metric or count functions (for example,
high number of failed logins), the record document contains the typical
value and the anomalous value so we already have the baseline behavior.
For all job types, we grab the latest 3 anomalous documents. This is to
support the "Raw Evidence" portion of the expanded section in the
initial UI mockups. Note that the exact format of these documents may
change as we finalize the mockups but since this feature is behind a
feature flag, it should be ok to merge and finalize later.
<img width="463" height="374" alt="Screenshot 2026-05-18 at 4 12 06 PM"
src="https://github.com/user-attachments/assets/19af8c51-650d-40b2-8b9e-548daee8ac5e"
/>
## To Verify
1. Modify the default lookback period of the entity store logs
extraction task (because we're populating historical data)
```
--- a/x-pack/solutions/security/plugins/entity_store/server/domain/saved_objects/global_state/constants.ts
+++ b/x-pack/solutions/security/plugins/entity_store/server/domain/saved_objects/global_state/constants.ts
@@ -10,14 +10,14 @@ import { z } from '@kbn/zod/v4';
export const DEFAULT_HISTORY_SNAPSHOT_FREQUENCY = '24h';
export const LOG_EXTRACTION_DELAY_DEFAULT = '1m';
-export const LOG_EXTRACTION_LOOKBACK_PERIOD_DEFAULT = '3h';
+export const LOG_EXTRACTION_LOOKBACK_PERIOD_DEFAULT = '30d';
export const LOG_EXTRACTION_FREQUENCY_DEFAULT = '1m';
// Max amount of entities to extract in one ESQL query
export const LOG_EXTRACTION_DOCS_LIMIT_DEFAULT = 10000;
// Max raw log documents per logs to be processed in a query (inside elastic search)
export const LOG_EXTRACTION_MAX_LOGS_PER_PAGE_DEFAULT = 40000;
export const LOG_EXTRACTION_TIMEOUT_DEFAULT = '59s';
-export const LOG_EXTRACTION_MAX_TIME_WINDOW_SIZE_DEFAULT = '15m';
+export const LOG_EXTRACTION_MAX_TIME_WINDOW_SIZE_DEFAULT = '1d';
// Max total raw log documents to process per task run; 0 = no cap
```
2. Start ES and Kibana with the following feature flags:
```
uiSettings.overrides:
securitySolution:entityStoreEnableV2: true
xpack.securitySolution.enableExperimental:
- entityAnalyticsEntityStoreV2
- entityAnalyticsWatchlistEnabled
- entityAnalyticsNewHomePageEnabled
- leadGenerationEnabled
- entityAnalyticsMlJobBehaviorMaintainer ---->> !!! NEW FEATURE FLAG FOR THIS PR !!!
```
3. Use this script to populate some data:
https://gist.github.com/ymao1/d35d356f090e23c746055446cc21fba0. NOTE!!:
You may need to modify the Kibana URL if you're using a different base
path or SSL
You will need to also download these scripts that are referenced by the
above script.
- Rare region data:
https://gist.github.com/ymao1/3f8d1214928b5c27aa505a20b7f2425d
- High login count:
https://gist.github.com/ymao1/fbbdbcf7552455fd155ee52ffcddf67a
4. Verify the maintainer is started in Dev Tools
```
GET kbn:/internal/security/entity_store/entity_maintainers?apiVersion=2
```
Response should include the new `ml-anomaly-detection-jobs` maintainer
and the status should be `started`
```
{
"maintainers": [
{
"id": "ml-anomaly-detection-jobs",
"taskStatus": "started",
"interval": "1d",
"description": "Entity Analytics ML Anomaly Detection Maintainer",
"nextRunAt": "2026-05-19T12:30:27.957Z",
"minLicense": "platinum",
"customState": {},
"runs": 1,
"lastSuccessTimestamp": "2026-05-18T12:30:30.117Z",
"lastErrorTimestamp": null
},
]
}
```
5. Manually run the maintainer
```
POST kbn:/internal/security/entity_store/entity_maintainers/run/ml-anomaly-detection-jobs?apiVersion=2
```
You should see this info log when the maintainer is done:
```
[2026-05-19T17:45:00.929-04:00][INFO ][plugins.securitySolution.ml-anomaly-detection-jobs-default] Maintainer run completed in 2570ms
```
6. After the maintainer runs, you should see some entities populated
with behavior data
```
GET .entities.v2.latest.security_default-00001/_search
{
"query": {
"bool": {
"filter": [
{
"exists": {
"field": "entity.behaviors.anomaly_job_ids"
}
}
]
}
}
}
```
and you should see entries in the details index
```
GET .entity_analytics.ml-ad-jobs-latest-default/_search
```
---------
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>1 parent 3fd5f3b commit bfb1b79
28 files changed
Lines changed: 5206 additions & 6 deletions
File tree
- .buildkite/ftr-manifests
- x-pack/solutions/security
- plugins/security_solution
- common
- server
- lib/entity_analytics/maintainers/behaviors/ml_anomaly_detection
- __snapshots__
- test/security_solution_api_integration/test_suites/entity_analytics
- ml_anomaly_detection_maintainer/trial_license_complete_tier
- configs
- utils
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
129 | 129 | | |
130 | 130 | | |
131 | 131 | | |
| 132 | + | |
132 | 133 | | |
133 | 134 | | |
134 | 135 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
| 103 | + | |
103 | 104 | | |
104 | 105 | | |
105 | 106 | | |
| |||
Lines changed: 5 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
246 | 246 | | |
247 | 247 | | |
248 | 248 | | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
249 | 254 | | |
250 | 255 | | |
251 | 256 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 35 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
Lines changed: 86 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
0 commit comments