[AI4DSOC] [Attack discovery] Attack discovery alert updates (#219243)

### [AI4DSOC] [Attack discovery] Attack discovery alert updates

This PR is a follow up to [[AI4DSOC] [Attack discovery] Attack discovery alerts #218906](https://github.com/elastic/kibana/pull/218906) that:

- Adds actions to update the `kibana.alert.workflow_status` field, as illustrated by the screenshot below:

![kibana_alert_workflow_status](https://github.com/user-attachments/assets/62e30d16-4837-4f5a-8b1c-35b66dc2b587)

- Enables updates to the visibility (shared / not shared) field, `kibana.alert.attack_discovery.users`, as illustrated by the screenshot below:

![visibility_status](https://github.com/user-attachments/assets/3667aca6-3e30-430d-80c2-5aa89946b972)

#### Details

This PR:

- Implements feedback from <https://github.com/elastic/kibana/pull/218906>
- Fixes an issue where bulk inserts partially failed
- Fixes issues with `Connector` filter selection
- Updates the styling of deleted connectors in the connector filter

#### Feature flags

This feature is effected by the following required, recommended, and optional features flags:

### required: `securitySolution.attackDiscoveryAlertsEnabled`

Enable the required `securitySolution.attackDiscoveryAlertsEnabled` feature flag in `config/kibana.dev.yml`:

```yaml
feature_flags.overrides:
  securitySolution.attackDiscoveryAlertsEnabled: true
```

### recommended: `securitySolution.assistantAttackDiscoverySchedulingEnabled: true`

Also enable the recommended `assistantAttackDiscoverySchedulingEnabled` feature flag in `config/kibana.dev.yml`:

```yaml
feature_flags.overrides:
  securitySolution.attackDiscoveryAlertsEnabled: true
  securitySolution.assistantAttackDiscoverySchedulingEnabled: true
```

### optional

- To (optionally) test in the AI for SOC tier, add the following setting to `config/serverless.security.dev.yaml`:

```yaml
xpack.securitySolutionServerless.productTypes:
[
  { product_line: 'ai_soc', product_tier: 'search_ai_lake' },
]
```
This commit is contained in:
Andrew Macri 2025-04-25 21:45:20 -04:00 committed by GitHub
parent 0efbd19044
commit bbb8364de8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 2145 additions and 283 deletions

View file

@ -78,6 +78,7 @@ export const ATTACK_DISCOVERY_SCHEDULES_ENABLED_FEATURE_FLAG =
export const ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID = 'attack-discovery' as const;
export const ATTACK_DISCOVERY = `${ELASTIC_AI_ASSISTANT_INTERNAL_URL}/attack_discovery` as const;
export const ATTACK_DISCOVERY_BULK = `${ATTACK_DISCOVERY}/_bulk` as const;
export const ATTACK_DISCOVERY_BY_CONNECTOR_ID = `${ATTACK_DISCOVERY}/{connectorId}` as const;
export const ATTACK_DISCOVERY_CANCEL_BY_CONNECTOR_ID =
`${ATTACK_DISCOVERY}/cancel/{connectorId}` as const;

View file

@ -81,7 +81,7 @@ export const AttackDiscoveryFindResponse = z.object({
connector_names: z.array(z.string()),
data: z.array(AttackDiscoveryAlert),
page: z.number().int(),
perPage: z.number().int(),
per_page: z.number().int().optional(),
total: z.number().int(),
unique_alert_ids_count: z.number().int(),
});

View file

@ -122,7 +122,7 @@ paths:
$ref: './attack_discovery_alert.schema.yaml#/components/schemas/AttackDiscoveryAlert'
page:
type: integer
perPage:
per_page:
type: integer
total:
type: integer

View file

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Attack discovery bulk API
* version: 1
*/
import { z } from '@kbn/zod';
import { AttackDiscoveryAlert } from './attack_discovery_alert.gen';
export type PostAttackDiscoveryBulkRequestBody = z.infer<typeof PostAttackDiscoveryBulkRequestBody>;
export const PostAttackDiscoveryBulkRequestBody = z.object({
/**
* Parameters for the bulk API
*/
update: z
.object({
/**
* The IDs of the Attack discovery alerts to update
*/
ids: z.array(z.string()),
/**
* When provided, update the kibana.alert.workflow_status of the attack discovery alerts
*/
kibana_alert_workflow_status: z.enum(['open', 'acknowledged', 'closed']).optional(),
/**
* When provided, update the visibility of the alert, as determined by the kibana.alert.attack_discovery.users field
*/
visibility: z.enum(['not_shared', 'shared']).optional(),
})
.optional(),
});
export type PostAttackDiscoveryBulkRequestBodyInput = z.input<
typeof PostAttackDiscoveryBulkRequestBody
>;
export type PostAttackDiscoveryBulkResponse = z.infer<typeof PostAttackDiscoveryBulkResponse>;
export const PostAttackDiscoveryBulkResponse = z.object({
data: z.array(AttackDiscoveryAlert),
});

View file

@ -0,0 +1,73 @@
openapi: 3.0.0
info:
title: Attack discovery bulk API
version: '1'
paths:
/internal/elastic_assistant/attack_discovery/_bulk:
post:
x-codegen-enabled: true
x-labels: [ess, serverless]
operationId: PostAttackDiscoveryBulk
description: Bulk updates for attack discovery alerts
summary: Bulk updates for attack discovery alerts
tags:
- attack_discovery
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
update:
type: object
description: Parameters for the bulk API
required:
- ids
properties:
ids:
type: array
description: The IDs of the Attack discovery alerts to update
items:
type: string
kibana_alert_workflow_status:
type: string
description: When provided, update the kibana.alert.workflow_status of the attack discovery alerts
enum:
- open
- acknowledged
- closed
visibility:
type: string
description: When provided, update the visibility of the alert, as determined by the kibana.alert.attack_discovery.users field
enum:
- not_shared
- shared
responses:
200:
description: Successful response
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
type: array
items:
$ref: './attack_discovery_alert.schema.yaml#/components/schemas/AttackDiscoveryAlert'
400:
description: Bad request
content:
application/json:
schema:
type: object
properties:
statusCode:
type: number
error:
type: string
message:
type: string

View file

@ -23,6 +23,7 @@ export * from './common_attributes.gen';
// Attack discovery Schemas
export * from './attack_discovery/attack_discovery_alert.gen';
export * from './attack_discovery/post_attack_discovery_bulk.route.gen';
export * from './attack_discovery/find_attack_discoveries_route.gen';
export * from './attack_discovery/common_attributes.gen';
export * from './attack_discovery/get_attack_discovery_route.gen';

View file

@ -117,7 +117,10 @@ export const useFindAttackDiscoveries = ({
);
const getNextPageParam = useCallback((lastPage: AttackDiscoveryFindResponse) => {
const totalPages = Math.max(DEFAULT_PAGE, Math.ceil(lastPage.total / lastPage.perPage));
const totalPages = Math.max(
DEFAULT_PAGE,
Math.ceil(lastPage.total / (lastPage.per_page ?? DEFAULT_PER_PAGE))
);
if (totalPages === lastPage.page) {
return;

View file

@ -45,18 +45,21 @@ export const conversationsDataClientMock: {
};
const createAttackDiscoveryDataClientMock = (): AttackDiscoveryDataClientMock => ({
getAttackDiscovery: jest.fn(),
bulkUpdateAttackDiscoveryAlerts: jest.fn(),
createAttackDiscovery: jest.fn(),
createAttackDiscoveryAlerts: jest.fn(),
findAllAttackDiscoveries: jest.fn(),
getAlertConnectorNames: jest.fn(),
getAttackDiscovery: jest.fn(),
findAttackDiscoveryAlerts: jest.fn(),
findDocuments: jest.fn(),
findAttackDiscoveryByConnectorId: jest.fn(),
getAttackDiscoveryGenerations: jest.fn(),
getAttackDiscoveryGenerationById: jest.fn(),
updateAttackDiscovery: jest.fn(),
getReader: jest.fn(),
getWriter: jest.fn().mockResolvedValue({ bulk: jest.fn() }),
findDocuments: jest.fn(),
refreshEventLogIndex: jest.fn(),
updateAttackDiscovery: jest.fn(),
});
export const attackDiscoveryDataClientMock: {

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { AuthenticatedUser } from '@kbn/core/server';
export const mockAuthenticatedUser: AuthenticatedUser = {
enabled: true,
authentication_realm: {
name: 'test_authentication_realm_name',
type: 'saml',
},
lookup_realm: {
name: 'test_lookup_realm_name',
type: 'saml',
},
authentication_type: 'token',
authentication_provider: {
type: 'test_authentication_provider_type',
name: 'test_authentication_provider_name',
},
elastic_cloud_user: false,
profile_uid: 'u_EWATCHX9oIEsmcXj8aA1FkcaY3DE-XEpsiGTjrR2PmM_0',
roles: ['admin'],
username: 'test_user',
};

View file

@ -0,0 +1,453 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CreateAttackDiscoveryAlertsParams } from '@kbn/elastic-assistant-common';
export const mockCreateAttackDiscoveryAlertsParams: CreateAttackDiscoveryAlertsParams = {
alertsContextCount: 59,
anonymizedAlerts: [
{
pageContent:
'@timestamp,2025-04-23T06:10:57.341Z\n_id,156e8cceff47bbb4a17fe00b6a5c57500785cae79fd54944bb12e8a214b1778e\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-27T10:46:13.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/lsass.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,lsass.exe\nprocess.pid,2\nuser.domain,326lwhiqeg\nuser.name,c8db5601-e6fd-44b4-b645-21855715ce0a',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.340Z\n_id,b7c81c86e923a95211d73185afcfbb659d6cb7919fea74d2bce8186cbf15221d\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-26T06:49:04.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/lsass.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,lsass.exe\nprocess.pid,2\nuser.domain,6yedwajn9r\nuser.name,96908c11-7d55-48fe-a0dc-2a6de3b4736a',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.339Z\n_id,1f48440803875db9869fefdbc613adf3d39532b701c415eab6b715b16c3891da\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T18:26:41.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/notepad.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,notepad.exe\nprocess.pid,2\nuser.domain,s6t47lzoo0\nuser.name,256b6a76-729e-42de-abb1-1b1583f1b0ba',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.338Z\n_id,0614e695ff78ec8b96e46a96e4f52023e738cd8d4ef710a0eb9bbc05f90f6921\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T12:54:11.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/iexlorer.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,iexlorer.exe\nprocess.pid,2\nuser.domain,uvae2bp3di\nuser.name,e3a53869-c1be-459f-bf07-26ad89afc110',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.337Z\n_id,966029c8fb82a63c5c6aaa274b7e23da87a34d5a6e854d8b6f3f6047126d35ea\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T11:24:20.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,ffozn2oulm\nuser.name,d90db9ba-e964-4cf2-a88d-0f1b0fbffea1',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.333Z\n_id,696ceb46588c6f0a191a528f57d63a22e8d0565fc856e88ccefb5c72fd60d600\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T10:07:33.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,q6g9jm2hmj\nuser.name,1b326a75-a58c-4be5-bc1a-099cd1058622',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.331Z\n_id,3ba7ccb776ab533cb50362d32dfccd5d556995bedd50bfbd34a817e65217d1cf\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\ndestination.ip,10.227.128.134\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T10:01:46.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/powershell.exe\nprocess.name,powershell.exe\nprocess.pid,2\nsource.ip,10.165.5.78\nuser.domain,levs2xh93i\nuser.name,905a6fce-581f-4dc5-ae4a-80d2a3a9cd8a',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.329Z\n_id,5fc5f124671b3f3088af216dcecd21fafc8021d09ea7bb70ed50d3de94040a14\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T09:08:04.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,j73wh73k7i\nuser.name,1c1fbea6-bbf8-4d64-a32d-190b5b91a7c8',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.328Z\n_id,3c8f95424a0ec986f7b1621481c6259d30335a9b288e622797075a73eec94cb0\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T09:00:52.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/mimikatz.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,mimikatz.exe\nprocess.pid,2\nuser.domain,pwn614fwgb\nuser.name,9bdca919-4ebf-419d-b6e8-d8b44a49daf9',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.326Z\n_id,27dab2e163455af4b040c98bd65916969a6f4e1c8dbf50f6880673c5c765f62d\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T08:48:33.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,nh1pp6oqry\nuser.name,66617ac5-ae65-411e-85af-2f23641033a7',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.324Z\n_id,04dcc5faeab7091560c83b50a8ef98f36241a4f8a6035912ac28db1447001b79\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T08:12:17.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/lsass.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,lsass.exe\nprocess.pid,2\nuser.domain,rn5hf8gstf\nuser.name,b53edb8b-ff02-4b8d-820a-0cd9e0a42ec8',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.323Z\n_id,49be8b9b46c1fca7477efc0142244b8645833c9d8a57c373c893ebda61fd957d\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T07:39:05.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,hgnwhoszak\nuser.name,0e936e8d-054b-473c-92f0-edfad248e727',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.322Z\n_id,36937b55716b9fcd7f0c444f0e104344cb79a450224b3d8bbeaa71b08462b2d5\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T07:22:59.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/powershell.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,powershell.exe\nprocess.pid,2\nuser.domain,6pgl3qjaek\nuser.name,25ecb7b0-3a2f-4d93-923e-1f740151859c',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.321Z\n_id,b95da0cffa5eaaa5f9f7d881507cb483d99a5cb58ad2b3e577864db2da8925f2\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T07:07:08.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/explorer.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,explorer.exe\nprocess.pid,2\nuser.domain,0equ0j19g9\nuser.name,7de4b044-34f0-4e58-a35a-08dca8be8e63',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.319Z\n_id,f1ce48bdc87632f2706466f57e78f3fb3db32de97280de339e1d2f9dd7d5eb22\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\ndestination.ip,10.246.31.27\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T06:53:37.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/notepad.exe\nprocess.name,notepad.exe\nprocess.pid,2\nsource.ip,10.110.157.207\nuser.domain,rnm07o8wqh\nuser.name,a88a40cc-f3d9-41aa-b931-5f32e13e3214',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-23T06:10:57.315Z\n_id,0bfb465a31735e41caf5a200c9fb8bfd9d0e9ec91d4ddc55379bbf141782c07a\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-23T06:10:38.423Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,tngs9s0401\nuser.name,790155dd-c99a-4241-b497-433e36f0d8b5',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:32.012Z\n_id,7dc9f8ff9f220685b0af680ad98d8dfd96ddaf749998600df6c18acbbc84a700\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-26T01:43:10.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/lsass.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,lsass.exe\nprocess.pid,2\nuser.domain,326lwhiqeg\nuser.name,c8db5601-e6fd-44b4-b645-21855715ce0a',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:32.011Z\n_id,7b52c0c4d1116d3c25109b98eb078b2acd320e458a28a64cea4f106ed3ac265a\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-24T21:46:01.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/lsass.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,lsass.exe\nprocess.pid,2\nuser.domain,6yedwajn9r\nuser.name,96908c11-7d55-48fe-a0dc-2a6de3b4736a',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:32.010Z\n_id,b6190597d82b333b5429c28c2f21e03f75560a584a53ae5a9816dd06b3fcb86f\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-22T03:51:08.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/iexlorer.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,iexlorer.exe\nprocess.pid,2\nuser.domain,uvae2bp3di\nuser.name,e3a53869-c1be-459f-bf07-26ad89afc110',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:32.010Z\n_id,6633e460477846d193d8a1ae3162ec9dfc744d9d33debaf866e5c964cb2334ad\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-22T09:23:38.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/notepad.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,notepad.exe\nprocess.pid,2\nuser.domain,s6t47lzoo0\nuser.name,256b6a76-729e-42de-abb1-1b1583f1b0ba',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:32.008Z\n_id,6ec6180501138d849e7a5838d9cd4baf01e0631989a9cba2081b91957cd97214\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-22T02:21:17.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,ffozn2oulm\nuser.name,d90db9ba-e964-4cf2-a88d-0f1b0fbffea1',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:32.007Z\n_id,cd01c83a7f1225309421b2b72f0ec27d08bb37bf4f00ad346ca120c0bc43fa9a\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-22T01:04:30.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,q6g9jm2hmj\nuser.name,1b326a75-a58c-4be5-bc1a-099cd1058622',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:32.006Z\n_id,1c2e28cfe7e27cf1291a71b933c4d9fcc51e6fb720600f5f8d736f70eedf320c\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\ndestination.ip,10.227.128.134\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-22T00:58:43.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/powershell.exe\nprocess.name,powershell.exe\nprocess.pid,2\nsource.ip,10.165.5.78\nuser.domain,levs2xh93i\nuser.name,905a6fce-581f-4dc5-ae4a-80d2a3a9cd8a',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:32.004Z\n_id,f5d06dc221a00850b28f89705495c483f4404896d80e22f353155ceb9a981ca8\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-22T00:05:01.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,j73wh73k7i\nuser.name,1c1fbea6-bbf8-4d64-a32d-190b5b91a7c8',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:32.003Z\n_id,aff833708dcf8683a4c097c9d39122b3e631b6dc67cc434ae536b323464dc896\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-21T23:57:49.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/mimikatz.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,mimikatz.exe\nprocess.pid,2\nuser.domain,pwn614fwgb\nuser.name,9bdca919-4ebf-419d-b6e8-d8b44a49daf9',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:32.001Z\n_id,16788a6dade98ee7ddb57c645a682cfb0e7cc84fcce8ea0b2b31a57a0b6f0b1c\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-21T23:45:30.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,nh1pp6oqry\nuser.name,66617ac5-ae65-411e-85af-2f23641033a7',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:31.997Z\n_id,537474e1556fd74aee77d0ed07bab8ba6100e2c0f1b802438fab4cb6f68fc35f\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-21T23:09:14.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/lsass.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,lsass.exe\nprocess.pid,2\nuser.domain,rn5hf8gstf\nuser.name,b53edb8b-ff02-4b8d-820a-0cd9e0a42ec8',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:31.995Z\n_id,61ce316f5316a83a4e221d2d70a8470503ef996f101a62bce6e09aa8cd1e0455\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-21T22:36:02.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,hgnwhoszak\nuser.name,0e936e8d-054b-473c-92f0-edfad248e727',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:31.994Z\n_id,1f7c734d31f7e269facc6800ec7f20ecc5d5feec343e16a6ba515aea79a5d8ab\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-21T22:19:56.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/powershell.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,powershell.exe\nprocess.pid,2\nuser.domain,6pgl3qjaek\nuser.name,25ecb7b0-3a2f-4d93-923e-1f740151859c',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:31.992Z\n_id,5f062b7ea32ef0ed4f83a44dd58768641db95b741c5ca71b44b126e7b200a25a\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-21T22:04:05.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/explorer.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,explorer.exe\nprocess.pid,2\nuser.domain,0equ0j19g9\nuser.name,7de4b044-34f0-4e58-a35a-08dca8be8e63',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:31.991Z\n_id,b30b887d7b06b7f570c86a7891a16ee1e9a9186baf5ba7776c4cf59cfb9e498e\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\ndestination.ip,10.246.31.27\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-21T21:50:34.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/notepad.exe\nprocess.name,notepad.exe\nprocess.pid,2\nsource.ip,10.110.157.207\nuser.domain,rnm07o8wqh\nuser.name,a88a40cc-f3d9-41aa-b931-5f32e13e3214',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T21:07:31.988Z\n_id,c9bf570db3b4b29719411926b227417d4a1350219e3529796e8180d66ba5a3b1\nagent.id,caaf3b95-7314-497c-8100-a13848468ee8\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,d25f9128-820e-4998-a26d-0c62507541b4\nhost.os.name,Linux\nhost.os.version,10.12\nkibana.alert.original_time,2025-04-21T21:07:35.313Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,tngs9s0401\nuser.name,790155dd-c99a-4241-b497-433e36f0d8b5',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.099Z\n_id,7796a78c19a9fb69af42c210146f0cb0e3d8e1fc0d62d18f193d83849193fd3e\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-05-01T22:20:08.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/lsass.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,lsass.exe\nprocess.pid,2\nuser.domain,zj54ebnoyj\nuser.name,a792fad8-dd2a-4627-87ac-9fc7de04471d',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.098Z\n_id,289010b8d3bba8afe4067033b025b9dbc54ccf9da0e78542d2dc876e5d724296\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-05-01T10:46:06.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/mimikatz.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,mimikatz.exe\nprocess.pid,2\nuser.domain,lzghdxpcuv\nuser.name,7a3be8c8-fd40-40cd-8889-1306911e5c1e',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.097Z\n_id,6edc81d2f62c1716a5e07509b283e3ef863cf7faddf4674c0a7bbf9ae8b65401\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-30T10:46:23.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/explorer.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,explorer.exe\nprocess.pid,2\nuser.domain,69ssexzvqs\nuser.name,d238fbd8-fda3-4b7d-b849-3f792e7f5ddc',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.096Z\n_id,bb305d416adfe5ab23a923371009d8fd9f5acf20d778ba4111b00c2d03b91bba\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-28T01:06:37.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/powershell.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,powershell.exe\nprocess.pid,2\nuser.domain,3kz7vv0xqy\nuser.name,82d518af-728f-44b3-9bf4-9155c119c663',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.095Z\n_id,f3c47435766e944288478a807e0924aeda9e06205798a184c55cf455356cab24\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\ndestination.ip,10.225.191.177\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-27T18:27:25.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/lsass.exe\nprocess.name,lsass.exe\nprocess.pid,2\nsource.ip,10.56.10.148\nuser.domain,ltg1iwt0x8\nuser.name,52bb5279-e2f3-4038-b0f6-dea8d2fa8cfc',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.094Z\n_id,99bf6115640aa5aeb9b4b0677189b11d88a67435ae9e69660e8060f3ae29030b\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-27T16:10:16.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/explorer.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,explorer.exe\nprocess.pid,2\nuser.domain,11ueu6seb0\nuser.name,8dfd9a11-b27a-4af8-b89c-b61821466a2a',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.093Z\n_id,ba5f45e8b9ff1b93113351c8d30d942cf7e07316573cb4998df5406ddafe53ce\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\ndestination.ip,10.174.76.173\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-27T15:44:17.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/notepad.exe\nprocess.name,notepad.exe\nprocess.pid,2\nsource.ip,10.186.165.114\nuser.domain,x0n46z9am5\nuser.name,9bcecb74-e2c2-42a3-b386-d10769cbf2cc',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.092Z\n_id,6db5ff7fbcf6229a7569a1a9b060f79e81ec4322025b05da7e161814f10aab1e\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-27T14:01:30.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,ilfcwy0jy4\nuser.name,fbbc0e7b-c391-44dd-b325-10f24f3180b2',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.091Z\n_id,f53996ffaf772c0239eea78bf84553dec201260d673a60944420187544d5dad3\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-24T15:25:06.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/mimikatz.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,mimikatz.exe\nprocess.pid,2\nuser.domain,ltpekvuk77\nuser.name,6cdbbe3e-f437-4160-a557-b8817406e2cb',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.089Z\n_id,a482c14498197c71f319e3a843ec4f9e7f4155b52b8b30e9b43a65a86ae609da\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-23T20:30:06.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,kz0c8xj11r\nuser.name,8da6ff34-4ca4-49fe-84c1-39dd76269255',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.088Z\n_id,c4d1a50454ff08e345563bfeb207d7fdc29a04b574e5494a888ee3b575b5ae1a\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-23T13:38:27.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,k5s82ra4p9\nuser.name,25aed668-b51c-428f-8ed7-cb7076074927',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.087Z\n_id,f3d3e63ddd1097354a5f85f7677cc5731ecad630dd6b601c4ed338afaa4ce7cd\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-23T10:11:20.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/lsass.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,lsass.exe\nprocess.pid,2\nuser.domain,y2fipbnbcs\nuser.name,2c92dc4a-ff8c-49e8-837e-118fa7ba5f2f',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.087Z\n_id,5d5a4f48b1eeb93a73ae06e50c3133910ad83dc6bcb37346225dfdca0d594c37\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\ndestination.ip,10.230.219.230\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-22T19:22:35.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/explorer.exe\nprocess.name,explorer.exe\nprocess.pid,2\nsource.ip,10.237.220.100\nuser.domain,q3p216qvyk\nuser.name,c2e9cd59-e481-4c3c-a8a9-bafdd5982826',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.086Z\n_id,75c66ed3e739b4e6e75f78d8eec8eaa793572a647f38e85c568aa53016b7dcc2\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-22T05:38:55.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,fyweudx4ln\nuser.name,68333292-b812-44de-b479-18a5b170925f',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.085Z\n_id,1aff0e2673adad409f2f9904008d231fc60c5867cfb3c98a07ca2b5c34b73635\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\ndestination.ip,10.62.70.34\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T21:15:51.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/lsass.exe\nprocess.name,lsass.exe\nprocess.pid,2\nsource.ip,10.86.116.72\nuser.domain,cpyq5md0rg\nuser.name,46a697fe-1606-42d4-a6c0-b9e2c89f624f',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.081Z\n_id,5582b1708a833daba3be23d2226b1a84e388583497d6ef7431664e97ef691528\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T20:55:42.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/powershell.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,powershell.exe\nprocess.pid,2\nuser.domain,ukfm9a4ooj\nuser.name,a00479ef-9240-422f-a1de-06de25cc9cfe',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.080Z\n_id,bfcf0b5311172f7f87d3a36b9ceaac5e1fc4200ad9aabb5ba1b0480f42dc4e57\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T20:33:35.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/mimikatz.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,mimikatz.exe\nprocess.pid,2\nuser.domain,hizhwsfk4p\nuser.name,37ad0f7f-fecf-45ae-b35b-f61a911ee38d',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.078Z\n_id,ad98fc453900826573ddca5eb19dab465be188f3a02c984c5cdda3b1ccec1d9c\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T20:13:30.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/powershell.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,powershell.exe\nprocess.pid,2\nuser.domain,h54oasjcw2\nuser.name,193565aa-2523-4da6-9df4-5f7225e0dc31',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.077Z\n_id,3b3a1d4633306591fdf3c9dcaa03a24f7ba9e1e63596a401ff0301beddbaeb07\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T19:47:06.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,gqbrxs3aqb\nuser.name,126fb618-7855-41ca-a079-ef1fb096ad4e',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.075Z\n_id,cbe63218d18c19f5a48ab0c95ee9018546783a05ccda1c2a58b39754a63d1f53\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T19:31:16.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,j025k9x0eo\nuser.name,8bc4314b-331a-458c-b167-bf355559e640',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.074Z\n_id,13a75fe5d870f7bc47df2b1051dce08f13a2778fcbe5415ecf080a86d2f10190\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T19:20:46.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/explorer.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,explorer.exe\nprocess.pid,2\nuser.domain,1dqz1rkull\nuser.name,59b435b6-8627-4b28-8010-e0702955f6fc',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.073Z\n_id,d6dc50546909bd9265d67089c527dc34518409f1ff458732a57d945f622f0bb1\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\ndestination.ip,10.54.98.80\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T18:11:08.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/notepad.exe\nprocess.name,notepad.exe\nprocess.pid,2\nsource.ip,10.217.217.199\nuser.domain,mdbs6ohcw4\nuser.name,d0c4fab3-df09-4564-b8ac-fadfcf35c4bf',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.071Z\n_id,1586c695d467889246ffc5e6ddfe3b46f52d6a4a0c0e453f25df2730a93dfc54\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nfile.hash.sha256,fake file sha256\nfile.name,fake_malware.exe\nfile.path,C:/fake_malware.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T17:36:19.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,system\nprocess.executable,C:/malware.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,malware writer\nprocess.pid,2\nuser.domain,d2iva941c9\nuser.name,04293bbf-1bba-47d3-9abf-e3e3e24e24f9',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.069Z\n_id,25e95160ade107aa1746594636995c4897a41fe4fab6d7f29a0a3e4caa2a63e4\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\ndestination.ip,10.120.137.144\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T17:07:20.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/iexlorer.exe\nprocess.name,iexlorer.exe\nprocess.pid,2\nsource.ip,10.4.228.9\nuser.domain,rh7bmovwfr\nuser.name,ec771845-f7df-4f1d-8ef4-30ea6991308b',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.068Z\n_id,d43710a39a3590a9ecebc7faecd33d6f04dc4891b5fc38702b3480d8becfd4aa\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T17:06:26.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/notepad.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,notepad.exe\nprocess.pid,2\nuser.domain,mz1vs5cfoj\nuser.name,589c83ce-2d23-44a4-8e9c-9ce3c9535481',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T17:00:32.065Z\n_id,daa409bfa90730dcc25181fc389669b86b2b1ec54c0d210769591bc5f73cdede\nagent.id,59af2e1a-095b-4cb6-9167-0f989f7c5b9d\ndestination.ip,10.137.112.54\nevent.category,behavior\nevent.dataset,endpoint.diagnostic.collection\nevent.module,endpoint\nfile.name,fake_behavior.exe\nfile.path,C:/fake_behavior.exe\nhost.name,ba933963-be1f-4d58-b1ff-1a3baff87ef8\nhost.os.name,macOS\nhost.os.version,12.6.1\nkibana.alert.original_time,2025-04-21T16:59:54.651Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.code_signature.status,trusted\nprocess.code_signature.subject_name,Microsoft Windows\nprocess.executable,C:/fake_behavior/powershell.exe\nprocess.name,powershell.exe\nprocess.pid,2\nsource.ip,10.67.234.101\nuser.domain,h8b4zaqzj3\nuser.name,e5a6c255-8251-408c-9952-2eb1c746a25a',
metadata: {},
},
{
pageContent:
'@timestamp,2025-04-21T16:56:32.164Z\n_id,2fe9c47ac368870c71adcb2092d0a3776fb57900135a73afbc697cd2fa558d9d\nagent.id,30cd4aa3-c908-49b7-befe-a9f6e21c03f5\nevent.category,malware\nevent.dataset,endpoint\nevent.module,endpoint\nhost.name,077c4701-48c5-4cce-adee-cef0dff53350\nhost.os.name,Windows\nhost.os.version,10.0\nkibana.alert.original_time,2025-04-21T16:55:29.520Z\nkibana.alert.risk_score,47\nkibana.alert.rule.description,Generates a detection alert each time an Elastic Defend alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.\nkibana.alert.rule.name,Endpoint Security (Elastic Defend)\nkibana.alert.severity,medium\nkibana.alert.workflow_status,open\nprocess.Ext.token.integrity_level_name,high\nprocess.executable,C:/fake/explorer.exe\nprocess.hash.md5,fake md5\nprocess.hash.sha1,fake sha1\nprocess.hash.sha256,fake sha256\nprocess.name,explorer.exe\nprocess.pid,2\nuser.domain,cn95jk9a10\nuser.name,af5c33f5-ebf0-486c-ba74-8ef9535e5c66',
metadata: {},
},
],
apiConfig: {
connectorId: 'pmeClaudeV37SonnetUsEast1',
actionTypeId: '.bedrock',
},
attackDiscoveries: [
{
alertIds: [
'0bfb465a31735e41caf5a200c9fb8bfd9d0e9ec91d4ddc55379bbf141782c07a',
'3ba7ccb776ab533cb50362d32dfccd5d556995bedd50bfbd34a817e65217d1cf',
'5fc5f124671b3f3088af216dcecd21fafc8021d09ea7bb70ed50d3de94040a14',
'3c8f95424a0ec986f7b1621481c6259d30335a9b288e622797075a73eec94cb0',
'27dab2e163455af4b040c98bd65916969a6f4e1c8dbf50f6880673c5c765f62d',
'04dcc5faeab7091560c83b50a8ef98f36241a4f8a6035912ac28db1447001b79',
'49be8b9b46c1fca7477efc0142244b8645833c9d8a57c373c893ebda61fd957d',
'36937b55716b9fcd7f0c444f0e104344cb79a450224b3d8bbeaa71b08462b2d5',
'b95da0cffa5eaaa5f9f7d881507cb483d99a5cb58ad2b3e577864db2da8925f2',
'f1ce48bdc87632f2706466f57e78f3fb3db32de97280de339e1d2f9dd7d5eb22',
'0614e695ff78ec8b96e46a96e4f52023e738cd8d4ef710a0eb9bbc05f90f6921',
'1f48440803875db9869fefdbc613adf3d39532b701c415eab6b715b16c3891da',
'966029c8fb82a63c5c6aaa274b7e23da87a34d5a6e854d8b6f3f6047126d35ea',
'696ceb46588c6f0a191a528f57d63a22e8d0565fc856e88ccefb5c72fd60d600',
'c9bf570db3b4b29719411926b227417d4a1350219e3529796e8180d66ba5a3b1',
'b30b887d7b06b7f570c86a7891a16ee1e9a9186baf5ba7776c4cf59cfb9e498e',
'5f062b7ea32ef0ed4f83a44dd58768641db95b741c5ca71b44b126e7b200a25a',
'1f7c734d31f7e269facc6800ec7f20ecc5d5feec343e16a6ba515aea79a5d8ab',
'61ce316f5316a83a4e221d2d70a8470503ef996f101a62bce6e09aa8cd1e0455',
'537474e1556fd74aee77d0ed07bab8ba6100e2c0f1b802438fab4cb6f68fc35f',
'16788a6dade98ee7ddb57c645a682cfb0e7cc84fcce8ea0b2b31a57a0b6f0b1c',
'aff833708dcf8683a4c097c9d39122b3e631b6dc67cc434ae536b323464dc896',
'f5d06dc221a00850b28f89705495c483f4404896d80e22f353155ceb9a981ca8',
'1c2e28cfe7e27cf1291a71b933c4d9fcc51e6fb720600f5f8d736f70eedf320c',
'cd01c83a7f1225309421b2b72f0ec27d08bb37bf4f00ad346ca120c0bc43fa9a',
'6ec6180501138d849e7a5838d9cd4baf01e0631989a9cba2081b91957cd97214',
'6633e460477846d193d8a1ae3162ec9dfc744d9d33debaf866e5c964cb2334ad',
'b6190597d82b333b5429c28c2f21e03f75560a584a53ae5a9816dd06b3fcb86f',
],
detailsMarkdown:
'## Persistent Attack Campaign on Linux Host\n\nA sophisticated persistent attack campaign was identified on {{ host.name d25f9128-820e-4998-a26d-0c62507541b4 }} spanning multiple days:\n\n* **Initial Compromise (April 21, 21:07:35)**: The campaign began with {{ process.name malware writer }} execution with {{ process.Ext.token.integrity_level_name system }} privileges by user {{ user.name 790155dd-c99a-4241-b497-433e36f0d8b5 }}.\n\n* **Network Reconnaissance (April 21, 21:50:34)**: The attacker used {{ process.name notepad.exe }} to establish connections from {{ source.ip 10.110.157.207 }} to {{ destination.ip 10.246.31.27 }} for command and control.\n\n* **Lateral Movement (April 21, 22:04:05)**: {{ process.name explorer.exe }} was executed by user {{ user.name 7de4b044-34f0-4e58-a35a-08dca8be8e63 }} to establish persistence.\n\n* **Command Execution (April 21, 22:19:56)**: {{ process.name powershell.exe }} was executed by user {{ user.name 25ecb7b0-3a2f-4d93-923e-1f740151859c }}.\n\n* **Credential Access (April 21, 23:09:14)**: {{ process.name lsass.exe }} was executed by user {{ user.name b53edb8b-ff02-4b8d-820a-0cd9e0a42ec8 }} for credential harvesting.\n\n* **Privilege Escalation (April 21, 23:57:49)**: {{ process.name mimikatz.exe }} was executed by user {{ user.name 9bdca919-4ebf-419d-b6e8-d8b44a49daf9 }}.\n\n* **Command and Control (April 22, 00:58:43)**: {{ process.name powershell.exe }} established connections from {{ source.ip 10.165.5.78 }} to {{ destination.ip 10.227.128.134 }}.\n\n* **Second Wave (April 23, 06:10:38)**: The attackers returned with another execution of {{ process.name malware writer }} using the same user {{ user.name 790155dd-c99a-4241-b497-433e36f0d8b5 }} and same system privileges.\n\n* **Continued Network Activity (April 23, 06:53:37)**: The same pattern of {{ process.name notepad.exe }} connecting from {{ source.ip 10.110.157.207 }} to {{ destination.ip 10.246.31.27 }} was observed.\n\n* **Additional Credential Harvesting (April 23, 09:00:52)**: {{ process.name mimikatz.exe }} was executed again by user {{ user.name 9bdca919-4ebf-419d-b6e8-d8b44a49daf9 }}.\n\n* **Data Collection and Exfiltration (April 23, 18:26:41)**: The campaign culminated with {{ process.name notepad.exe }} being used for likely data exfiltration.',
entitySummaryMarkdown:
'Persistent attack campaign on {{ host.name d25f9128-820e-4998-a26d-0c62507541b4 }} spanning April 21-23 with consistent TTPs and user accounts',
mitreAttackTactics: [
'Initial Access',
'Execution',
'Persistence',
'Privilege Escalation',
'Credential Access',
'Discovery',
'Lateral Movement',
'Collection',
'Command and Control',
'Exfiltration',
],
summaryMarkdown:
'Multi-day attack campaign on {{ host.name d25f9128-820e-4998-a26d-0c62507541b4 }} from April 21-23 showing consistent patterns of {{ process.name malware writer }} execution, network reconnaissance, credential harvesting with {{ process.name mimikatz.exe }}, and data exfiltration.',
title: 'Linux Host Persistent Attack Campaign',
timestamp: '2025-04-24T17:36:25.632Z',
},
{
alertIds: [
'd43710a39a3590a9ecebc7faecd33d6f04dc4891b5fc38702b3480d8becfd4aa',
'daa409bfa90730dcc25181fc389669b86b2b1ec54c0d210769591bc5f73cdede',
'1586c695d467889246ffc5e6ddfe3b46f52d6a4a0c0e453f25df2730a93dfc54',
'25e95160ade107aa1746594636995c4897a41fe4fab6d7f29a0a3e4caa2a63e4',
'13a75fe5d870f7bc47df2b1051dce08f13a2778fcbe5415ecf080a86d2f10190',
'd6dc50546909bd9265d67089c527dc34518409f1ff458732a57d945f622f0bb1',
'cbe63218d18c19f5a48ab0c95ee9018546783a05ccda1c2a58b39754a63d1f53',
'3b3a1d4633306591fdf3c9dcaa03a24f7ba9e1e63596a401ff0301beddbaeb07',
'ad98fc453900826573ddca5eb19dab465be188f3a02c984c5cdda3b1ccec1d9c',
'bfcf0b5311172f7f87d3a36b9ceaac5e1fc4200ad9aabb5ba1b0480f42dc4e57',
'5582b1708a833daba3be23d2226b1a84e388583497d6ef7431664e97ef691528',
'1aff0e2673adad409f2f9904008d231fc60c5867cfb3c98a07ca2b5c34b73635',
],
detailsMarkdown:
'## macOS Host Attack Chain\n\nA distinct attack chain was observed on macOS host {{ host.name ba933963-be1f-4d58-b1ff-1a3baff87ef8 }}:\n\n* **Initial Access (April 21, 16:59:54)**: The attack began with {{ process.name powershell.exe }} establishing connections from {{ source.ip 10.67.234.101 }} to {{ destination.ip 10.137.112.54 }}.\n\n* **Execution (April 21, 17:06:26)**: {{ process.name notepad.exe }} was executed by user {{ user.name 589c83ce-2d23-44a4-8e9c-9ce3c9535481 }}.\n\n* **Discovery (April 21, 17:07:20)**: {{ process.name iexlorer.exe }} established connections from {{ source.ip 10.4.228.9 }} to {{ destination.ip 10.120.137.144 }}.\n\n* **Persistence (April 21, 17:36:19)**: {{ process.name malware writer }} was executed with {{ process.Ext.token.integrity_level_name system }} privileges by user {{ user.name 04293bbf-1bba-47d3-9abf-e3e3e24e24f9 }}.\n\n* **Lateral Movement (April 21, 18:11:08)**: {{ process.name notepad.exe }} established connections from {{ source.ip 10.217.217.199 }} to {{ destination.ip 10.54.98.80 }}.\n\n* **Privilege Escalation (April 21, 19:20:46)**: {{ process.name explorer.exe }} was executed by user {{ user.name 59b435b6-8627-4b28-8010-e0702955f6fc }}.\n\n* **Data Collection (April 21, 19:31:16 - 19:47:06)**: Multiple instances of {{ process.name malware writer }} were executed with system privileges.\n\n* **Command and Control (April 21, 20:13:30 - 20:55:42)**: Multiple instances of {{ process.name powershell.exe }} were executed by different users.\n\n* **Credential Access (April 21, 20:33:35)**: {{ process.name mimikatz.exe }} was executed by user {{ user.name 37ad0f7f-fecf-45ae-b35b-f61a911ee38d }}.\n\n* **Exfiltration (April 21, 21:15:51)**: {{ process.name lsass.exe }} established connections from {{ source.ip 10.86.116.72 }} to {{ destination.ip 10.62.70.34 }}, likely for data exfiltration.',
entitySummaryMarkdown:
'Attack chain on macOS {{ host.name ba933963-be1f-4d58-b1ff-1a3baff87ef8 }} involving multiple users and malicious processes',
mitreAttackTactics: [
'Initial Access',
'Execution',
'Persistence',
'Privilege Escalation',
'Credential Access',
'Discovery',
'Lateral Movement',
'Collection',
'Command and Control',
'Exfiltration',
],
summaryMarkdown:
'Sophisticated attack on macOS {{ host.name ba933963-be1f-4d58-b1ff-1a3baff87ef8 }} beginning with {{ process.name powershell.exe }} establishing C2 connections, followed by discovery, persistence, credential access with {{ process.name mimikatz.exe }}, and data exfiltration.',
title: 'macOS Host Advanced Persistent Threat',
timestamp: '2025-04-24T17:36:25.632Z',
},
],
connectorName: 'Claude 3.7 Sonnet',
generationUuid: 'bb848edc-975c-4f38-ba88-f6e03a23a41c',
replacements: {
'c8db5601-e6fd-44b4-b645-21855715ce0a': '5psx2nlvzs',
'd25f9128-820e-4998-a26d-0c62507541b4': 'Host-2jo95wayl3',
'96908c11-7d55-48fe-a0dc-2a6de3b4736a': 'bwryodcxgq',
'256b6a76-729e-42de-abb1-1b1583f1b0ba': 'wsztb7vumo',
'e3a53869-c1be-459f-bf07-26ad89afc110': 'piwro06jtx',
'd90db9ba-e964-4cf2-a88d-0f1b0fbffea1': '2p45oufte4',
'1b326a75-a58c-4be5-bc1a-099cd1058622': 'b1vlxdnqif',
'905a6fce-581f-4dc5-ae4a-80d2a3a9cd8a': 'glgomwyzbm',
'1c1fbea6-bbf8-4d64-a32d-190b5b91a7c8': 'w52wr3mf6h',
'9bdca919-4ebf-419d-b6e8-d8b44a49daf9': 'vjmicfxk1w',
'66617ac5-ae65-411e-85af-2f23641033a7': 'iv6ippiktf',
'b53edb8b-ff02-4b8d-820a-0cd9e0a42ec8': 'uo0elxwqht',
'0e936e8d-054b-473c-92f0-edfad248e727': 'llam90iqjg',
'25ecb7b0-3a2f-4d93-923e-1f740151859c': 'rvutmk7jpw',
'7de4b044-34f0-4e58-a35a-08dca8be8e63': 'dtqtruuieh',
'a88a40cc-f3d9-41aa-b931-5f32e13e3214': 'igkqtyoptp',
'790155dd-c99a-4241-b497-433e36f0d8b5': '3fb04q6crg',
'a792fad8-dd2a-4627-87ac-9fc7de04471d': 'ji4un060ft',
'ba933963-be1f-4d58-b1ff-1a3baff87ef8': 'Host-oq4z2o322c',
'7a3be8c8-fd40-40cd-8889-1306911e5c1e': '606mxklygd',
'd238fbd8-fda3-4b7d-b849-3f792e7f5ddc': 'vbvsh6zdo6',
'82d518af-728f-44b3-9bf4-9155c119c663': '25pivmp463',
'52bb5279-e2f3-4038-b0f6-dea8d2fa8cfc': '5z5ufz0ybm',
'8dfd9a11-b27a-4af8-b89c-b61821466a2a': 'l9iu7o9c6v',
'9bcecb74-e2c2-42a3-b386-d10769cbf2cc': 'hfi2fqzfel',
'fbbc0e7b-c391-44dd-b325-10f24f3180b2': '5aylswlg7p',
'6cdbbe3e-f437-4160-a557-b8817406e2cb': '7f2c0ct7f0',
'8da6ff34-4ca4-49fe-84c1-39dd76269255': 't5fnulvvu9',
'25aed668-b51c-428f-8ed7-cb7076074927': 'ebujhte0ob',
'2c92dc4a-ff8c-49e8-837e-118fa7ba5f2f': 'kzwi95tcjy',
'c2e9cd59-e481-4c3c-a8a9-bafdd5982826': 'mf10wxe3sd',
'68333292-b812-44de-b479-18a5b170925f': 'w2z8dlrp63',
'46a697fe-1606-42d4-a6c0-b9e2c89f624f': 'tpl8v0lv4i',
'a00479ef-9240-422f-a1de-06de25cc9cfe': '2pcdw35azv',
'37ad0f7f-fecf-45ae-b35b-f61a911ee38d': 'fd7g0zb5mq',
'193565aa-2523-4da6-9df4-5f7225e0dc31': 'jlomzbkc2w',
'126fb618-7855-41ca-a079-ef1fb096ad4e': '8jeqnknvof',
'8bc4314b-331a-458c-b167-bf355559e640': '05t3r3e2eq',
'59b435b6-8627-4b28-8010-e0702955f6fc': '5mivs1zqgn',
'd0c4fab3-df09-4564-b8ac-fadfcf35c4bf': 'mfpbbfq6u6',
'04293bbf-1bba-47d3-9abf-e3e3e24e24f9': 'chrlu391fy',
'ec771845-f7df-4f1d-8ef4-30ea6991308b': 'vnlwwxmjau',
'589c83ce-2d23-44a4-8e9c-9ce3c9535481': 'qe0v3cel3g',
'e5a6c255-8251-408c-9952-2eb1c746a25a': 'mr3ms78u3u',
'af5c33f5-ebf0-486c-ba74-8ef9535e5c66': 'fge8acmng2',
'077c4701-48c5-4cce-adee-cef0dff53350': 'Host-m1pewb1koa',
},
};

View file

@ -5,9 +5,7 @@
* 2.0.
*/
import type { FeatureFlagDefinitions } from '@kbn/core-feature-flags-server';
import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server';
import { ATTACK_DISCOVERY_ALERTS_ENABLED_FEATURE_FLAG } from '@kbn/elastic-assistant-common';
import { configSchema } from './config_schema';
export const config: PluginConfigDescriptor = {
@ -18,28 +16,6 @@ export async function plugin(initializerContext: PluginInitializerContext) {
return new ElasticAssistantPlugin(initializerContext);
}
export const featureFlags: FeatureFlagDefinitions = [
{
key: ATTACK_DISCOVERY_ALERTS_ENABLED_FEATURE_FLAG,
name: 'Saved Attack discoveries',
description: 'Experimental feature that allows users to save attack discoveries',
tags: ['attack-discovery', 'elastic-assistant'],
variationType: 'boolean',
variations: [
{
name: 'On',
description: 'Enables saved attack discoveries',
value: true,
},
{
name: 'Off',
description: 'Disables saved attack discoveries',
value: false,
},
],
},
];
export type {
ElasticAssistantPluginSetup,
ElasticAssistantPluginStart,

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { estypes } from '@elastic/elasticsearch';
import { AuthenticatedUser } from '@kbn/core-security-common';
import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils';
import { ALERT_ATTACK_DISCOVERY_USERS } from '../schedules/fields';
export const getUpdateAttackDiscoveryAlertsQuery = ({
authenticatedUser,
ids,
indexPattern,
kibanaAlertWorkflowStatus,
visibility,
}: {
authenticatedUser: AuthenticatedUser;
ids: string[];
indexPattern: string;
kibanaAlertWorkflowStatus?: 'acknowledged' | 'closed' | 'open';
visibility?: 'not_shared' | 'shared';
}): estypes.UpdateByQueryRequest => ({
allow_no_indices: true,
conflicts: 'proceed',
ignore_unavailable: true,
index: [indexPattern],
query: {
ids: {
values: ids,
},
},
script: {
source: `
if (params.kibanaAlertWorkflowStatus != null) {
ctx._source['${ALERT_WORKFLOW_STATUS}'] = params.kibanaAlertWorkflowStatus;
}
if (params.visibility == 'not_shared') {
ctx._source['${ALERT_ATTACK_DISCOVERY_USERS}'] = new ArrayList();
Map user = new HashMap();
user.put('id', params.authenticatedUser.profile_uid);
user.put('name', params.authenticatedUser.username);
ctx._source['${ALERT_ATTACK_DISCOVERY_USERS}'].add(user);
} else if (params.visibility == 'shared') {
ctx._source['${ALERT_ATTACK_DISCOVERY_USERS}'] = new ArrayList();
}
`,
params: {
authenticatedUser: {
profile_uid: authenticatedUser.profile_uid,
username: authenticatedUser.username,
},
kibanaAlertWorkflowStatus,
visibility,
},
},
});

View file

@ -57,9 +57,7 @@ export const combineFindAttackDiscoveryFilters = ({
...(connectorNames && connectorNames.length > 0
? [
`(${connectorNames
.map(
(name) => `${ALERT_ATTACK_DISCOVERY_API_CONFIG_NAME}: "${escapeQueryString(name)}"`
)
.map((name) => `${ALERT_ATTACK_DISCOVERY_API_CONFIG_NAME}: "${name}"`)
.join(' OR ')})`,
]
: []),

View file

@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
import { loggerMock } from '@kbn/logging-mocks';
import { createAttackDiscoveryAlerts } from '.';
import { mockAuthenticatedUser } from '../../../../__mocks__/mock_authenticated_user';
import { mockCreateAttackDiscoveryAlertsParams } from '../../../../__mocks__/mock_create_attack_discovery_alerts_params';
describe('createAttackDiscoveryAlerts', () => {
const mockEsClient = elasticsearchServiceMock.createElasticsearchClient();
const mockLogger = loggerMock.create();
const mockNow = new Date('2025-04-24T17:36:25.812Z');
const spaceId = 'default';
const attackDiscoveryAlertsIndex = 'mock-index';
beforeEach(() => {
jest.resetAllMocks();
jest.spyOn(global, 'Date').mockImplementation(() => mockNow);
});
it('returns an empty array if no alert documents are created', async () => {
const mockParams = {
...mockCreateAttackDiscoveryAlertsParams,
attackDiscoveries: [],
};
const result = await createAttackDiscoveryAlerts({
attackDiscoveryAlertsIndex,
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockParams,
esClient: mockEsClient,
logger: mockLogger,
spaceId,
});
expect(result).toEqual([]);
});
it('throws an error if bulk insertion fails', async () => {
mockEsClient.bulk.mockResolvedValue({
items: [
{
create: {
_index: 'mock-index',
status: 400,
error: { reason: 'Test error', type: 'test_error' },
},
},
],
errors: true,
took: 1,
});
await expect(
createAttackDiscoveryAlerts({
attackDiscoveryAlertsIndex,
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
esClient: mockEsClient,
logger: mockLogger,
spaceId,
})
).rejects.toThrow('Failed to bulk insert Attack discovery alerts');
});
it('logs an error if fetching created alerts fails', async () => {
mockEsClient.bulk.mockResolvedValue({
items: [
{
create: {
result: 'created',
_id: 'mock-id-1',
_index: 'mock-index',
status: 201,
},
},
],
errors: false,
took: 1,
});
mockEsClient.search.mockRejectedValue(new Error('Search error'));
await expect(
createAttackDiscoveryAlerts({
attackDiscoveryAlertsIndex,
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
esClient: mockEsClient,
logger: mockLogger,
spaceId,
})
).rejects.toThrow('Search error');
expect(mockLogger.error).toHaveBeenCalledWith(
expect.stringContaining('Error getting created Attack discovery alerts')
);
});
});

View file

@ -84,6 +84,30 @@ export const createAttackDiscoveryAlerts = async ({
const createdDocumentIds = getCreatedDocumentIds(bulkResponse);
if (bulkResponse.errors) {
const errorDetails = bulkResponse.items.flatMap((item) => {
const error = item.create?.error;
if (error == null) {
return [];
}
const id = item.create?._id != null ? ` id: ${item.create._id}` : '';
const details = `\nError bulk inserting attack discovery alert${id} ${error.reason}`;
return [details];
});
const allErrorDetails = errorDetails.join(', ');
throw new Error(`Failed to bulk insert Attack discovery alerts ${allErrorDetails}`);
}
logger.debug(
() =>
`Created Attack discovery alerts in index ${attackDiscoveryAlertsIndex} with document ids: ${createdDocumentIds.join(
', '
)}`
);
return getCreatedAttackDiscoveryAlerts({
attackDiscoveryAlertsIndex,
createdDocumentIds,

View file

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getScheduledAndAdHocIndexPattern } from '.';
describe('getScheduledAndAdHocIndexPattern', () => {
it('returns the expected pattern for the `default` spaceId', () => {
const spaceId = 'default';
const result = getScheduledAndAdHocIndexPattern(spaceId);
expect(result).toBe(
'.alerts-security.attack.discovery.alerts-default,.alerts-security.attack.discovery.alerts-ad-hoc-default'
);
});
it('returns the expected pattern for a non-default spaceId', () => {
const spaceId = 'another-space';
const result = getScheduledAndAdHocIndexPattern(spaceId);
expect(result).toBe(
'.alerts-security.attack.discovery.alerts-another-space,.alerts-security.attack.discovery.alerts-ad-hoc-another-space'
);
});
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ATTACK_DISCOVERY_ALERTS_COMMON_INDEX_PREFIX } from '@kbn/elastic-assistant-common';
export const getScheduledAndAdHocIndexPattern = (spaceId: string): string =>
[
`${ATTACK_DISCOVERY_ALERTS_COMMON_INDEX_PREFIX}-${spaceId}`, // scheduled
`${ATTACK_DISCOVERY_ALERTS_COMMON_INDEX_PREFIX}-ad-hoc-${spaceId}`, // ad-hoc
].join(',');

View file

@ -15,7 +15,6 @@ import {
type AttackDiscoveryFindResponse,
type GetAttackDiscoveryGenerationsResponse,
type PostAttackDiscoveryGenerationsDismissResponse,
ATTACK_DISCOVERY_ALERTS_COMMON_INDEX_PREFIX,
} from '@kbn/elastic-assistant-common';
import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server';
import { AuthenticatedUser } from '@kbn/core-security-common';
@ -41,6 +40,8 @@ import { getFindAttackDiscoveryAlertsAggregation } from './get_find_attack_disco
import { AttackDiscoveryAlertDocument } from '../schedules/types';
import { transformSearchResponseToAlerts } from './transforms/transform_search_response_to_alerts';
import { IIndexPatternString } from '../../../types';
import { getScheduledAndAdHocIndexPattern } from './get_scheduled_and_ad_hoc_index_pattern';
import { getUpdateAttackDiscoveryAlertsQuery } from '../get_update_attack_discovery_alerts_query';
const FIRST_PAGE = 1; // CAUTION: sever-side API uses a 1-based page index convention (for consistency with similar existing APIs)
const DEFAULT_PER_PAGE = 10;
@ -129,6 +130,67 @@ export class AttackDiscoveryDataClient extends AIAssistantDataClient {
});
};
// Runs an aggregation only bound to the (optional) alertIds and date range
// to prevent the connector names from being filtered-out as the user applies more filters:
public getAlertConnectorNames = async ({
alertIds,
authenticatedUser,
end,
ids,
index,
logger,
page,
perPage,
sortField,
sortOrder,
start,
}: {
alertIds: string[] | undefined;
authenticatedUser: AuthenticatedUser;
end: string | undefined;
ids: string[] | undefined;
index: string;
logger: Logger;
page: number;
perPage: number;
sortField: string;
sortOrder: string;
start: string | undefined;
}): Promise<string[]> => {
const aggs = getFindAttackDiscoveryAlertsAggregation();
// just use the (optional) alertIds and date range to prevent the connector
// names from being filtered-out as the user applies more filters:
const connectorsAggsFilter = combineFindAttackDiscoveryFilters({
alertIds, // optional
end,
ids,
start,
});
const combinedConnectorsAggsFilter = getCombinedFilter({
authenticatedUser,
filter: connectorsAggsFilter,
});
const aggsResult = await this.findDocuments<AttackDiscoveryAlertDocument>({
aggs,
filter: combinedConnectorsAggsFilter,
index,
page,
perPage,
sortField,
sortOrder,
});
const { connectorNames } = transformSearchResponseToAlerts({
logger,
response: aggsResult.data,
});
return connectorNames;
};
public findAttackDiscoveryAlerts = async ({
authenticatedUser,
findAttackDiscoveryAlertsParams,
@ -139,10 +201,9 @@ export class AttackDiscoveryDataClient extends AIAssistantDataClient {
logger: Logger;
}): Promise<AttackDiscoveryFindResponse> => {
const aggs = getFindAttackDiscoveryAlertsAggregation();
const {
alertIds,
connectorNames,
connectorNames, // <-- as a filter input
end,
ids,
search,
@ -155,6 +216,9 @@ export class AttackDiscoveryDataClient extends AIAssistantDataClient {
perPage = DEFAULT_PER_PAGE,
} = findAttackDiscoveryAlertsParams;
const spaceId = this.spaceId;
const index = getScheduledAndAdHocIndexPattern(spaceId);
const filter = combineFindAttackDiscoveryFilters({
alertIds,
connectorNames,
@ -171,8 +235,6 @@ export class AttackDiscoveryDataClient extends AIAssistantDataClient {
shared,
});
const index = `${ATTACK_DISCOVERY_ALERTS_COMMON_INDEX_PREFIX}-*`;
const result = await this.findDocuments<AttackDiscoveryAlertDocument>({
aggs,
filter: combinedFilter,
@ -183,25 +245,45 @@ export class AttackDiscoveryDataClient extends AIAssistantDataClient {
sortOrder,
});
const {
connectorNames: alertConnectorNames,
data,
uniqueAlertIdsCount,
} = transformSearchResponseToAlerts({
const { data, uniqueAlertIdsCount } = transformSearchResponseToAlerts({
logger,
response: result.data,
});
const alertConnectorNames = await this.getAlertConnectorNames({
alertIds,
authenticatedUser,
end,
ids,
index,
logger,
page,
perPage,
sortField,
sortOrder,
start,
});
return {
connector_names: alertConnectorNames,
connector_names: alertConnectorNames, // <-- from the separate aggregation
data,
page: result.page,
perPage: result.perPage,
per_page: result.perPage,
total: result.total,
unique_alert_ids_count: uniqueAlertIdsCount,
};
};
public async refreshEventLogIndex(eventLogIndex: string): Promise<void> {
const esClient = await this.options.elasticsearchClientPromise;
await esClient.indices.refresh({
allow_no_indices: true,
ignore_unavailable: true,
index: eventLogIndex,
});
}
public getAttackDiscoveryGenerations = async ({
authenticatedUser,
eventLogIndex,
@ -242,6 +324,93 @@ export class AttackDiscoveryDataClient extends AIAssistantDataClient {
});
};
public bulkUpdateAttackDiscoveryAlerts = async ({
authenticatedUser,
ids,
kibanaAlertWorkflowStatus,
logger,
visibility,
}: {
authenticatedUser: AuthenticatedUser;
ids: string[];
kibanaAlertWorkflowStatus?: 'acknowledged' | 'closed' | 'open';
logger: Logger;
visibility?: 'not_shared' | 'shared';
}): Promise<AttackDiscoveryAlert[]> => {
const PER_PAGE = 1000;
const esClient = await this.options.elasticsearchClientPromise;
const indexPattern = getScheduledAndAdHocIndexPattern(this.spaceId);
if (ids.length === 0) {
logger.debug(
() =>
`No Attack discovery alerts to update for index ${indexPattern} in bulkUpdateAttackDiscoveryAlerts`
);
return [];
}
try {
logger.debug(
() =>
`Updating Attack discovery alerts in index ${indexPattern} with alert ids: ${ids.join(
', '
)}`
);
const updateByQuery = getUpdateAttackDiscoveryAlertsQuery({
authenticatedUser,
ids,
indexPattern,
kibanaAlertWorkflowStatus,
visibility,
});
const updateResponse = await esClient.updateByQuery(updateByQuery);
await esClient.indices.refresh({
allow_no_indices: true,
ignore_unavailable: true,
index: indexPattern,
});
if (updateResponse.failures != null && updateResponse.failures.length > 0) {
const errorDetails = updateResponse.failures.flatMap((failure) => {
const error = failure?.cause;
if (error == null) {
return [];
}
const id = failure.id != null ? ` id: ${failure.id}` : '';
const details = `\nError updating attack discovery alert${id} ${error}`;
return [details];
});
const allErrorDetails = errorDetails.join(', ');
throw new Error(`Failed to update attack discovery alerts ${allErrorDetails}`);
}
const alertsResult = await this.findAttackDiscoveryAlerts({
authenticatedUser,
findAttackDiscoveryAlertsParams: {
ids,
page: FIRST_PAGE,
perPage: PER_PAGE,
sortField: '@timestamp',
},
logger,
});
return alertsResult.data;
} catch (err) {
logger.error(`Error updating Attack discovery alerts: ${err} for ids: ${ids.join(', ')}`);
throw err;
}
};
public getAttackDiscoveryGenerationById = async ({
authenticatedUser,
eventLogIndex,

View file

@ -79,7 +79,7 @@ export const transformGetAttackDiscoveryGenerationsSearchResult = ({
status,
};
} catch (e) {
logger.warn(
logger.debug(
() =>
`Skipping Attack discovery generation search result for execution ${
executionUuid != null ? executionUuid : 'unknown executionUuid'

View file

@ -22,8 +22,8 @@ export const getAlertRiskScore = ({
// _id: abcd1234,
// kibana.alert.risk_score: 50,
const idRegex = /^\w*_id,\s*(.*)\w*\n?/gm; // extracts the alert ID
const riskScoreRegex = /^\w*kibana\.alert\.risk_score,\s*(\d+)\w*\n?/gm; // extracts the risk score
const idRegex = /^\w*_id,\s*(.*)\w*\n?/m; // extracts the alert ID
const riskScoreRegex = /^\w*kibana\.alert\.risk_score,\s*(\d+)\w*\n?/m; // extracts the risk score
const alertIdRiskScoreMap: Record<string, number> = anonymizedAlerts.reduce((acc, alert) => {
const idMatch = idRegex.exec(alert.pageContent);

View file

@ -0,0 +1,280 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
replaceAnonymizedValuesWithOriginalValues,
ATTACK_DISCOVERY_AD_HOC_RULE_TYPE_ID,
ATTACK_DISCOVERY_AD_HOC_RULE_ID,
type CreateAttackDiscoveryAlertsParams,
} from '@kbn/elastic-assistant-common';
import {
ALERT_INSTANCE_ID,
ALERT_RULE_CATEGORY,
ALERT_RULE_NAME,
ALERT_RULE_TYPE_ID,
ALERT_RULE_UUID,
ALERT_UUID,
} from '@kbn/rule-data-utils';
import {
ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN_WITH_REPLACEMENTS,
ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS,
ALERT_ATTACK_DISCOVERY_REPLACEMENTS,
ALERT_ATTACK_DISCOVERY_USER_ID,
ALERT_ATTACK_DISCOVERY_USER_NAME,
ALERT_ATTACK_DISCOVERY_USERS,
ALERT_RISK_SCORE,
ALERT_ATTACK_DISCOVERY_ALERT_IDS,
ALERT_ATTACK_DISCOVERY_ALERTS_CONTEXT_COUNT,
} from '../../../schedules/fields/field_names';
import { v4 as uuidv4 } from 'uuid';
import { transformToAlertDocuments } from '.';
import { mockAuthenticatedUser } from '../../../../../__mocks__/mock_authenticated_user';
import { mockCreateAttackDiscoveryAlertsParams } from '../../../../../__mocks__/mock_create_attack_discovery_alerts_params';
jest.mock('uuid', () => ({
v4: jest.fn(),
}));
describe('transformToAlertDocuments', () => {
const mockNow = new Date('2025-04-24T17:36:25.812Z');
const spaceId = 'default';
beforeEach(() => {
jest.resetAllMocks();
(uuidv4 as unknown as jest.Mock)
.mockImplementationOnce(() => '879B171F-428B-4B23-99C3-EDE33334AB71')
.mockImplementationOnce(() => '123B171F-428B-4B23-99C3-EDE33334AB72');
});
it(`returns the expected ${ALERT_ATTACK_DISCOVERY_ALERT_IDS} field`, () => {
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_ATTACK_DISCOVERY_ALERT_IDS]).toEqual(
mockCreateAttackDiscoveryAlertsParams.attackDiscoveries[0].alertIds
);
});
it(`returns an empty array for ${ALERT_ATTACK_DISCOVERY_ALERT_IDS} if no alertIds are provided`, () => {
const params = {
...mockCreateAttackDiscoveryAlertsParams,
attackDiscoveries: [
{
...mockCreateAttackDiscoveryAlertsParams.attackDiscoveries[0],
alertIds: [],
},
],
};
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: params,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_ATTACK_DISCOVERY_ALERT_IDS]).toEqual([]);
});
it(`returns the expected ${ALERT_ATTACK_DISCOVERY_ALERTS_CONTEXT_COUNT} field`, () => {
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_ATTACK_DISCOVERY_ALERTS_CONTEXT_COUNT]).toEqual(
mockCreateAttackDiscoveryAlertsParams.alertsContextCount
);
});
it(`returns the expected ${ALERT_UUID}`, () => {
uuidv4 as unknown as jest.Mock;
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_UUID]).toBe('879B171F-428B-4B23-99C3-EDE33334AB71');
});
it(`returns the same ${ALERT_INSTANCE_ID} as the ${ALERT_UUID}`, () => {
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_INSTANCE_ID]).toEqual(result[0][ALERT_UUID]);
});
it(`returns the expected ${ALERT_RISK_SCORE}`, () => {
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_RISK_SCORE]).toEqual(1316);
});
it(`returns the expected ${ALERT_ATTACK_DISCOVERY_USER_ID}`, () => {
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_ATTACK_DISCOVERY_USER_ID]).toEqual(mockAuthenticatedUser.profile_uid);
});
it(`returns the expected ${ALERT_ATTACK_DISCOVERY_USER_NAME}`, () => {
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_ATTACK_DISCOVERY_USER_NAME]).toEqual(mockAuthenticatedUser.username);
});
it(`returns the expected ${ALERT_ATTACK_DISCOVERY_USERS}`, () => {
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_ATTACK_DISCOVERY_USERS]).toEqual([
{
id: mockAuthenticatedUser.profile_uid,
name: mockAuthenticatedUser.username,
},
]);
});
it('generates unique UUIDs for multiple alerts', () => {
const params = {
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
};
const result = transformToAlertDocuments(params);
const uuids = result.map((alert) => alert[ALERT_UUID]);
expect(uuids).toEqual([
'879B171F-428B-4B23-99C3-EDE33334AB71',
'123B171F-428B-4B23-99C3-EDE33334AB72',
]);
});
it(`returns the expected ${ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN_WITH_REPLACEMENTS}`, () => {
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN_WITH_REPLACEMENTS]).toEqual(
replaceAnonymizedValuesWithOriginalValues({
messageContent: mockCreateAttackDiscoveryAlertsParams.attackDiscoveries[0].detailsMarkdown,
replacements: mockCreateAttackDiscoveryAlertsParams.replacements ?? {},
})
);
});
it('handles undefined entitySummaryMarkdown correctly', () => {
const params: CreateAttackDiscoveryAlertsParams = {
...mockCreateAttackDiscoveryAlertsParams,
attackDiscoveries: [
{
...mockCreateAttackDiscoveryAlertsParams.attackDiscoveries[0],
entitySummaryMarkdown: undefined, // <-- undefined
},
],
};
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: params,
now: mockNow,
spaceId,
});
expect(
result[0][ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS]
).toBeUndefined();
});
it(`returns the expected ${ALERT_ATTACK_DISCOVERY_REPLACEMENTS}`, () => {
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_ATTACK_DISCOVERY_REPLACEMENTS]).toEqual(
Object.entries(mockCreateAttackDiscoveryAlertsParams.replacements ?? {}).map(
([uuid, value]) => ({
uuid,
value,
})
)
);
});
it('returns the expected static ALERT_RULE* fields', () => {
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: mockCreateAttackDiscoveryAlertsParams,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_RULE_CATEGORY]).toBe(
'Attack discovery ad hoc (placeholder rule category)'
);
expect(result[0][ALERT_RULE_NAME]).toBe('Attack discovery ad hoc (placeholder rule name)');
expect(result[0][ALERT_RULE_TYPE_ID]).toBe(ATTACK_DISCOVERY_AD_HOC_RULE_TYPE_ID);
expect(result[0][ALERT_RULE_UUID]).toBe(ATTACK_DISCOVERY_AD_HOC_RULE_ID);
});
it('handles empty replacements correctly', () => {
const params = {
...mockCreateAttackDiscoveryAlertsParams,
replacements: {},
};
const result = transformToAlertDocuments({
authenticatedUser: mockAuthenticatedUser,
createAttackDiscoveryAlertsParams: params,
now: mockNow,
spaceId,
});
expect(result[0][ALERT_ATTACK_DISCOVERY_REPLACEMENTS]).toBeUndefined();
});
});

View file

@ -10,7 +10,7 @@ import { AuthenticatedUser } from '@kbn/core/server';
import {
ATTACK_DISCOVERY_AD_HOC_RULE_ID,
ATTACK_DISCOVERY_AD_HOC_RULE_TYPE_ID,
CreateAttackDiscoveryAlertsParams,
type CreateAttackDiscoveryAlertsParams,
replaceAnonymizedValuesWithOriginalValues,
} from '@kbn/elastic-assistant-common';
import {
@ -78,8 +78,6 @@ export const transformToAlertDocuments = ({
const replacementsOrEmpty = replacements ?? {};
const alertUuid = uuidv4();
return attackDiscoveries.map(
({
alertIds,
@ -88,76 +86,82 @@ export const transformToAlertDocuments = ({
mitreAttackTactics,
summaryMarkdown,
title,
}) => ({
'@timestamp': now.toISOString(),
[ALERT_ATTACK_DISCOVERY_ALERT_IDS]: alertIds,
[ALERT_ATTACK_DISCOVERY_ALERTS_CONTEXT_COUNT]: alertsContextCount,
[ALERT_ATTACK_DISCOVERY_API_CONFIG]: {
action_type_id: apiConfig.actionTypeId,
connector_id: apiConfig.connectorId,
model: apiConfig.model,
name: connectorName,
provider: apiConfig.provider,
},
[ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN]: detailsMarkdown,
[ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN_WITH_REPLACEMENTS]:
replaceAnonymizedValuesWithOriginalValues({
messageContent: detailsMarkdown,
replacements: replacementsOrEmpty,
}),
[ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN]: entitySummaryMarkdown,
[ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS]:
entitySummaryMarkdown != null
? replaceAnonymizedValuesWithOriginalValues({
messageContent: entitySummaryMarkdown,
replacements: replacementsOrEmpty,
})
: undefined,
[ALERT_ATTACK_DISCOVERY_MITRE_ATTACK_TACTICS]: mitreAttackTactics,
[ALERT_ATTACK_DISCOVERY_REPLACEMENTS]: !isEmpty(replacementsOrEmpty)
? Object.entries(replacementsOrEmpty).map(([uuid, value]) => ({
uuid,
value,
}))
: undefined,
[ALERT_ATTACK_DISCOVERY_SUMMARY_MARKDOWN]: summaryMarkdown,
[ALERT_ATTACK_DISCOVERY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS]:
replaceAnonymizedValuesWithOriginalValues({
messageContent: summaryMarkdown,
replacements: replacementsOrEmpty,
}),
[ALERT_ATTACK_DISCOVERY_TITLE]: title,
[ALERT_ATTACK_DISCOVERY_TITLE_WITH_REPLACEMENTS]: replaceAnonymizedValuesWithOriginalValues({
messageContent: title,
replacements: replacementsOrEmpty,
}),
[ALERT_ATTACK_DISCOVERY_USER_ID]: authenticatedUser.profile_uid,
[ALERT_ATTACK_DISCOVERY_USER_NAME]: authenticatedUser.username,
[ALERT_ATTACK_DISCOVERY_USERS]: [
{
id: authenticatedUser.profile_uid,
name: authenticatedUser.username,
}) => {
const alertUuid = uuidv4();
return {
'@timestamp': now.toISOString(),
[ALERT_ATTACK_DISCOVERY_ALERT_IDS]: alertIds,
[ALERT_ATTACK_DISCOVERY_ALERTS_CONTEXT_COUNT]: alertsContextCount,
[ALERT_ATTACK_DISCOVERY_API_CONFIG]: {
action_type_id: apiConfig.actionTypeId,
connector_id: apiConfig.connectorId,
model: apiConfig.model,
name: connectorName,
provider: apiConfig.provider,
},
],
[ALERT_RULE_EXECUTION_UUID]: generationUuid,
[ALERT_INSTANCE_ID]: alertUuid,
[ALERT_RISK_SCORE]: getAlertRiskScore({
alertIds,
anonymizedAlerts,
}),
[ALERT_RULE_CATEGORY]: 'Attack discovery ad hoc (placeholder rule category)',
[ALERT_RULE_CONSUMER]: 'siem',
[ALERT_RULE_NAME]: 'Attack discovery ad hoc (placeholder rule name)',
[ALERT_RULE_PRODUCER]: 'siem',
[ALERT_RULE_REVISION]: 1,
[ALERT_RULE_TYPE_ID]: ATTACK_DISCOVERY_AD_HOC_RULE_TYPE_ID, // sentinel value
[ALERT_RULE_UUID]: ATTACK_DISCOVERY_AD_HOC_RULE_ID, // sentinel value
[ALERT_STATUS]: 'active',
[ALERT_UUID]: alertUuid, // IMPORTANT: the document _id should be the same as this field when it's bulk inserted
[ALERT_WORKFLOW_STATUS]: 'open',
[ECS_VERSION]: EcsVersion,
[EVENT_KIND]: 'signal',
[SPACE_IDS]: [spaceId],
})
[ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN]: detailsMarkdown,
[ALERT_ATTACK_DISCOVERY_DETAILS_MARKDOWN_WITH_REPLACEMENTS]:
replaceAnonymizedValuesWithOriginalValues({
messageContent: detailsMarkdown,
replacements: replacementsOrEmpty,
}),
[ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN]: entitySummaryMarkdown,
[ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS]:
entitySummaryMarkdown != null
? replaceAnonymizedValuesWithOriginalValues({
messageContent: entitySummaryMarkdown,
replacements: replacementsOrEmpty,
})
: undefined,
[ALERT_ATTACK_DISCOVERY_MITRE_ATTACK_TACTICS]: mitreAttackTactics,
[ALERT_ATTACK_DISCOVERY_REPLACEMENTS]: !isEmpty(replacementsOrEmpty)
? Object.entries(replacementsOrEmpty).map(([uuid, value]) => ({
uuid,
value,
}))
: undefined,
[ALERT_ATTACK_DISCOVERY_SUMMARY_MARKDOWN]: summaryMarkdown,
[ALERT_ATTACK_DISCOVERY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS]:
replaceAnonymizedValuesWithOriginalValues({
messageContent: summaryMarkdown,
replacements: replacementsOrEmpty,
}),
[ALERT_ATTACK_DISCOVERY_TITLE]: title,
[ALERT_ATTACK_DISCOVERY_TITLE_WITH_REPLACEMENTS]: replaceAnonymizedValuesWithOriginalValues(
{
messageContent: title,
replacements: replacementsOrEmpty,
}
),
[ALERT_ATTACK_DISCOVERY_USER_ID]: authenticatedUser.profile_uid,
[ALERT_ATTACK_DISCOVERY_USER_NAME]: authenticatedUser.username,
[ALERT_ATTACK_DISCOVERY_USERS]: [
{
id: authenticatedUser.profile_uid,
name: authenticatedUser.username,
},
],
[ALERT_RULE_EXECUTION_UUID]: generationUuid,
[ALERT_INSTANCE_ID]: alertUuid,
[ALERT_RISK_SCORE]: getAlertRiskScore({
alertIds,
anonymizedAlerts,
}),
[ALERT_RULE_CATEGORY]: 'Attack discovery ad hoc (placeholder rule category)',
[ALERT_RULE_CONSUMER]: 'siem',
[ALERT_RULE_NAME]: 'Attack discovery ad hoc (placeholder rule name)',
[ALERT_RULE_PRODUCER]: 'siem',
[ALERT_RULE_REVISION]: 1,
[ALERT_RULE_TYPE_ID]: ATTACK_DISCOVERY_AD_HOC_RULE_TYPE_ID, // sentinel value
[ALERT_RULE_UUID]: ATTACK_DISCOVERY_AD_HOC_RULE_ID, // sentinel value
[ALERT_STATUS]: 'active',
[ALERT_UUID]: alertUuid, // IMPORTANT: the document _id should be the same as this field when it's bulk inserted
[ALERT_WORKFLOW_STATUS]: 'open',
[ECS_VERSION]: EcsVersion,
[EVENT_KIND]: 'signal',
[SPACE_IDS]: [spaceId],
};
}
);
};

View file

@ -20,8 +20,6 @@ export const ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN =
`${ALERT_ATTACK_DISCOVERY}.entity_summary_markdown` as const;
export const ALERT_ATTACK_DISCOVERY_ENTITY_SUMMARY_MARKDOWN_WITH_REPLACEMENTS =
`${ALERT_ATTACK_DISCOVERY}.entity_summary_markdown_with_replacements` as const;
export const ALERT_ATTACK_DISCOVERY_GENERATION_UUID =
`${ALERT_ATTACK_DISCOVERY}.generation_uuid` as const;
export const ALERT_ATTACK_DISCOVERY_MITRE_ATTACK_TACTICS =
`${ALERT_ATTACK_DISCOVERY}.mitre_attack_tactics` as const;
export const ALERT_ATTACK_DISCOVERY_SUMMARY_MARKDOWN =

View file

@ -9,18 +9,21 @@ import { type IEventLogger } from '@kbn/event-log-plugin/server';
import { AuthenticatedUser } from '@kbn/core/server';
import { ATTACK_DISCOVERY_EVENT_PROVIDER } from '../../../../../../common/constants';
import { AttackDiscoveryDataClient } from '../../../../../lib/attack_discovery/persistence';
const MAX_LENGTH = 1024;
export const writeAttackDiscoveryEvent = ({
export const writeAttackDiscoveryEvent = async ({
action,
alertsContextCount,
attackDiscoveryAlertsEnabled,
authenticatedUser,
connectorId,
dataClient,
duration,
end,
eventLogger,
eventLogIndex,
executionUuid,
loadingMessage,
message,
@ -39,6 +42,8 @@ export const writeAttackDiscoveryEvent = ({
| 'generation-dismissed';
/** The number of alerts sent as context to the LLM for the generation */
alertsContextCount?: number;
/** This client is used to wait for an event log refresh */
dataClient: AttackDiscoveryDataClient | null;
/** Feature flag */
attackDiscoveryAlertsEnabled: boolean;
/** The authenticated user generating Attack discoveries */
@ -51,6 +56,8 @@ export const writeAttackDiscoveryEvent = ({
end?: Date;
/** Event log writer */
eventLogger: IEventLogger;
/** Event log index (to refresh) */
eventLogIndex: string;
/** The unique identifier (kibana.alert.rule.execution.uuid) for the generation */
executionUuid: string;
/** The loading message (kibana.alert.rule.execution.status) logged for the generation */
@ -131,7 +138,10 @@ export const writeAttackDiscoveryEvent = ({
},
};
// console.log('--> logging event', JSON.stringify(attackDiscoveryEvent, null, 2));
eventLogger.logEvent(attackDiscoveryEvent);
try {
eventLogger.logEvent(attackDiscoveryEvent);
} finally {
await dataClient?.refreshEventLogIndex(eventLogIndex);
}
}
};

View file

@ -75,6 +75,7 @@ export const postAttackDiscoveryRoute = (
const resp = buildResponse(response);
const assistantContext = await context.elasticAssistant;
const { featureFlags } = await context.core;
const eventLogIndex = (await context.elasticAssistant).eventLogIndex;
const logger: Logger = assistantContext.logger;
const telemetry = assistantContext.telemetry;
@ -157,12 +158,14 @@ export const postAttackDiscoveryRoute = (
start: request.body.start,
});
writeAttackDiscoveryEvent({
await writeAttackDiscoveryEvent({
action: ATTACK_DISCOVERY_EVENT_LOG_ACTION_GENERATION_STARTED,
attackDiscoveryAlertsEnabled,
authenticatedUser,
connectorId,
dataClient,
eventLogger,
eventLogIndex,
executionUuid,
loadingMessage,
message: `Attack discovery generation ${executionUuid} for user ${authenticatedUser.username} started`,
@ -183,7 +186,7 @@ export const postAttackDiscoveryRoute = (
savedObjectsClient,
telemetry,
})
.then((result) => {
.then(async (result) => {
const end = new Date();
const durationNanoseconds = getDurationNanoseconds({
end,
@ -192,15 +195,17 @@ export const postAttackDiscoveryRoute = (
// NOTE: the (legacy) implementation of generateAttackDiscoveries returns an "error" object when failures occur (instead of rejecting):
if (result.error == null) {
writeAttackDiscoveryEvent({
await writeAttackDiscoveryEvent({
action: ATTACK_DISCOVERY_EVENT_LOG_ACTION_GENERATION_SUCCEEDED,
alertsContextCount: result.anonymizedAlerts?.length,
attackDiscoveryAlertsEnabled,
authenticatedUser,
connectorId,
dataClient,
duration: durationNanoseconds,
end,
eventLogger,
eventLogIndex,
executionUuid,
message: `Attack discovery generation ${executionUuid} for user ${authenticatedUser.username} succeeded`,
newAlerts: result.attackDiscoveries?.length ?? 0,
@ -208,15 +213,17 @@ export const postAttackDiscoveryRoute = (
spaceId,
});
} else {
writeAttackDiscoveryEvent({
await writeAttackDiscoveryEvent({
action: ATTACK_DISCOVERY_EVENT_LOG_ACTION_GENERATION_FAILED,
alertsContextCount: result.anonymizedAlerts?.length,
attackDiscoveryAlertsEnabled,
authenticatedUser,
connectorId,
dataClient,
duration: durationNanoseconds,
end,
eventLogger,
eventLogIndex,
executionUuid,
message: `Attack discovery generation ${executionUuid} for user ${authenticatedUser.username} failed: ${result.error?.message}`,
outcome: 'failure',
@ -225,7 +232,7 @@ export const postAttackDiscoveryRoute = (
});
}
})
.catch((error) => {
.catch(async (error) => {
// This is a fallback in case the promise is rejected (in a future implementation):
const end = new Date();
const durationNanoseconds = getDurationNanoseconds({
@ -233,14 +240,16 @@ export const postAttackDiscoveryRoute = (
start: generatedStarted,
});
writeAttackDiscoveryEvent({
await writeAttackDiscoveryEvent({
action: ATTACK_DISCOVERY_EVENT_LOG_ACTION_GENERATION_FAILED,
attackDiscoveryAlertsEnabled,
authenticatedUser,
connectorId,
dataClient,
duration: durationNanoseconds,
end,
eventLogger,
eventLogIndex,
executionUuid,
message: `Attack discovery generation ${executionUuid} for user ${authenticatedUser.username} failed: ${error.message}`,
outcome: 'failure',

View file

@ -0,0 +1,129 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { IKibanaResponse, IRouter, Logger } from '@kbn/core/server';
import { transformError } from '@kbn/securitysolution-es-utils';
import {
API_VERSIONS,
ATTACK_DISCOVERY_ALERTS_ENABLED_FEATURE_FLAG,
ATTACK_DISCOVERY_BULK,
PostAttackDiscoveryBulkRequestBody,
PostAttackDiscoveryBulkResponse,
} from '@kbn/elastic-assistant-common';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import { performChecks } from '../../helpers';
import { buildResponse } from '../../../lib/build_response';
import { ElasticAssistantRequestHandlerContext } from '../../../types';
export const postAttackDiscoveryBulkRoute = (
router: IRouter<ElasticAssistantRequestHandlerContext>
): void => {
router.versioned
.post({
access: 'internal',
path: ATTACK_DISCOVERY_BULK,
security: {
authz: {
requiredPrivileges: ['elasticAssistant'],
},
},
})
.addVersion(
{
version: API_VERSIONS.internal.v1,
validate: {
request: {
body: buildRouteValidationWithZod(PostAttackDiscoveryBulkRequestBody),
},
response: {
200: {
body: {
custom: buildRouteValidationWithZod(PostAttackDiscoveryBulkResponse),
},
},
},
},
},
async (
context,
request,
response
): Promise<IKibanaResponse<PostAttackDiscoveryBulkResponse>> => {
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
const resp = buildResponse(response);
const assistantContext = await context.elasticAssistant;
const { featureFlags } = await context.core;
const logger: Logger = assistantContext.logger;
// Perform license and authenticated user checks:
const checkResponse = await performChecks({
context: ctx,
request,
response,
});
if (!checkResponse.isSuccess) {
return checkResponse.response;
}
const attackDiscoveryAlertsEnabled = await featureFlags.getBooleanValue(
ATTACK_DISCOVERY_ALERTS_ENABLED_FEATURE_FLAG,
false
);
if (!attackDiscoveryAlertsEnabled) {
return resp.error({
body: `Attack discovery alerts feature is disabled`,
statusCode: 403,
});
}
try {
const currentUser = await checkResponse.currentUser;
const dataClient = await assistantContext.getAttackDiscoveryDataClient();
if (!dataClient) {
return resp.error({
body: `Attack discovery data client not initialized`,
statusCode: 500,
});
}
const kibanaAlertWorkflowStatus = request.body.update?.kibana_alert_workflow_status;
const visibility = request.body.update?.visibility;
const ids = request.body.update?.ids;
if (ids == null || ids.length === 0) {
return response.ok({
body: { data: [] },
});
}
const data = await dataClient.bulkUpdateAttackDiscoveryAlerts({
authenticatedUser: currentUser,
ids,
kibanaAlertWorkflowStatus,
logger,
visibility,
});
return response.ok({
body: { data },
});
} catch (err) {
logger.error(err);
const error = transformError(err);
return resp.error({
body: { success: false, error: error.message },
statusCode: error.statusCode,
});
}
}
);
};

View file

@ -94,6 +94,13 @@ export const postAttackDiscoveryGenerationsDismissRoute = (
false
);
if (!attackDiscoveryAlertsEnabled) {
return resp.error({
body: `Attack discovery alerts feature is disabled`,
statusCode: 403,
});
}
const previousGeneration = await dataClient.getAttackDiscoveryGenerationById({
authenticatedUser: currentUser,
eventLogIndex,
@ -105,12 +112,14 @@ export const postAttackDiscoveryGenerationsDismissRoute = (
// event log details:
const connectorId = previousGeneration.connector_id;
writeAttackDiscoveryEvent({
await writeAttackDiscoveryEvent({
action: ATTACK_DISCOVERY_EVENT_LOG_ACTION_GENERATION_DISMISSED,
attackDiscoveryAlertsEnabled,
authenticatedUser: currentUser,
connectorId,
dataClient,
eventLogger,
eventLogIndex,
executionUuid,
loadingMessage: undefined,
message: `Attack discovery generation ${executionUuid} for user ${currentUser.username} started`,

View file

@ -12,6 +12,7 @@ import { cancelAttackDiscoveryRoute } from './attack_discovery/post/cancel/cance
import { findAttackDiscoveriesRoute } from './attack_discovery/get/find_attack_discoveries';
import { getAttackDiscoveryRoute } from './attack_discovery/get/get_attack_discovery';
import { postAttackDiscoveryRoute } from './attack_discovery/post/post_attack_discovery';
import { postAttackDiscoveryBulkRoute } from './attack_discovery/post/post_attack_discovery_bulk';
import { ElasticAssistantPluginRouter } from '../types';
import { createConversationRoute } from './user_conversations/create_route';
import { deleteConversationRoute } from './user_conversations/delete_route';
@ -111,6 +112,7 @@ export const registerRoutes = (
// Attack Discovery
findAttackDiscoveriesRoute(router);
postAttackDiscoveryBulkRoute(router);
getAttackDiscoveryGenerationsRoute(router);
postAttackDiscoveryGenerationsDismissRoute(router);
getAttackDiscoveryRoute(router);

View file

@ -62,7 +62,6 @@
"@kbn/rule-data-utils",
"@kbn/alerting-types",
"@kbn/zod-helpers",
"@kbn/core-feature-flags-server",
],
"exclude": [
"target/**/*",

View file

@ -220,7 +220,7 @@ const AttackDiscoveryPageComponent: React.FC = () => {
const filter = parseFilterQuery({ filterQuery, kqlError });
try {
return fetchAttackDiscoveries({
return await fetchAttackDiscoveries({
end,
filter, // <-- combined search bar query and filters
size,

View file

@ -15,7 +15,7 @@ import {
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import React, { useCallback, useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import type { GenerationInterval } from '@kbn/elastic-assistant-common';
import { useKibana } from '../../../common/lib/kibana';
@ -169,19 +169,24 @@ const LoadingCalloutComponent: React.FC<Props> = ({
const { mutateAsync: dismissAttackDiscoveryGeneration } = useDismissAttackDiscoveryGeneration();
const [isDismissing, setIsDismissing] = React.useState(false);
const [isDismissing, setIsDismissing] = useState(false);
const dismissGeneration = useCallback(async () => {
try {
if (executionUuid != null) {
setIsDismissing(true);
await dismissAttackDiscoveryGeneration({ executionUuid });
await dismissAttackDiscoveryGeneration({ attackDiscoveryAlertsEnabled, executionUuid });
refetchGenerations?.(); // force a refresh of the generations list
}
} catch {
} finally {
setIsDismissing(false);
}
}, [dismissAttackDiscoveryGeneration, executionUuid, refetchGenerations]);
}, [
attackDiscoveryAlertsEnabled,
dismissAttackDiscoveryGeneration,
executionUuid,
refetchGenerations,
]);
return (
<div

View file

@ -15,6 +15,7 @@ import { mockAttackDiscovery } from '../../mock/mock_attack_discovery';
const defaultProps = {
attackDiscovery: mockAttackDiscovery,
isSelected: false,
setSelectedAttackDiscoveries: jest.fn(),
};
describe('AttackDiscoveryPanel', () => {

View file

@ -23,6 +23,7 @@ interface Props {
initialIsOpen?: boolean;
isSelected: boolean;
setIsSelected?: ({ id, selected }: { id: string; selected: boolean }) => void;
setSelectedAttackDiscoveries: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
onToggle?: (newState: 'open' | 'closed') => void;
replacements?: Replacements;
showAnonymized?: boolean;
@ -33,6 +34,7 @@ const AttackDiscoveryPanelComponent: React.FC<Props> = ({
initialIsOpen,
isSelected,
setIsSelected,
setSelectedAttackDiscoveries,
onToggle,
replacements,
showAnonymized = false,
@ -54,6 +56,7 @@ const AttackDiscoveryPanelComponent: React.FC<Props> = ({
onToggle={onToggle}
replacements={replacements}
setIsOpen={setIsOpen}
setSelectedAttackDiscoveries={setSelectedAttackDiscoveries}
showAnonymized={showAnonymized}
/>

View file

@ -24,6 +24,7 @@ interface Props {
onToggle?: (newState: 'open' | 'closed') => void;
replacements?: Replacements;
setIsOpen: React.Dispatch<React.SetStateAction<'open' | 'closed'>>;
setSelectedAttackDiscoveries: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
showAnonymized?: boolean;
}
@ -35,6 +36,7 @@ const PanelHeaderComponent: React.FC<Props> = ({
onToggle,
replacements,
setIsOpen,
setSelectedAttackDiscoveries,
showAnonymized = false,
}) => (
<EuiFlexGroup
@ -59,7 +61,11 @@ const PanelHeaderComponent: React.FC<Props> = ({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<SummaryActions attackDiscovery={attackDiscovery} replacements={replacements} />
<SummaryActions
attackDiscovery={attackDiscovery}
replacements={replacements}
setSelectedAttackDiscoveries={setSelectedAttackDiscoveries}
/>
</EuiFlexItem>
</EuiFlexGroup>
);

View file

@ -20,7 +20,9 @@ import { css } from '@emotion/react';
import { isEmpty } from 'lodash/fp';
import React, { useCallback, useMemo, useState } from 'react';
import { useAttackDiscoveryBulk } from '../../../../../../use_attack_discovery_bulk';
import { isAttackDiscoveryAlert } from '../../../../../../utils/is_attack_discovery_alert';
import { useKibanaFeatureFlags } from '../../../../../../use_kibana_feature_flags';
import * as i18n from './translations';
const LIST_PROPS = {
@ -37,6 +39,7 @@ interface SharedBadgeOptionData {
}
const SharedBadgeComponent: React.FC<Props> = ({ attackDiscovery }) => {
const { attackDiscoveryAlertsEnabled } = useKibanaFeatureFlags();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const onBadgeButtonClick = useCallback(() => {
@ -69,7 +72,6 @@ const SharedBadgeComponent: React.FC<Props> = ({ attackDiscovery }) => {
data: {
description: i18n.ONLY_VISIBLE_TO_YOU,
},
disabled: true,
'data-test-subj': 'notShared',
label: i18n.NOT_SHARED,
},
@ -78,12 +80,17 @@ const SharedBadgeComponent: React.FC<Props> = ({ attackDiscovery }) => {
data: {
description: i18n.VISIBLE_TO_YOUR_TEAM,
},
disabled: true,
'data-test-subj': 'shared',
label: i18n.SHARED,
},
]);
const selectedLabel = useMemo(() => {
const firstSelected = items.find((item) => item.checked === 'on');
return firstSelected != null ? firstSelected.label : items[0].label;
}, [items]);
const renderOption = useCallback(
(option: EuiSelectableOption<SharedBadgeOptionData>) => (
<EuiFlexGroup
@ -128,15 +135,29 @@ const SharedBadgeComponent: React.FC<Props> = ({ attackDiscovery }) => {
onClick={onBadgeButtonClick}
onClickAriaLabel={i18n.SELECT_VISIBILITY_ARIA_LABEL}
>
{isShared ? i18n.SHARED : i18n.NOT_SHARED}
{selectedLabel}
</EuiBadge>
),
[isShared, onBadgeButtonClick]
[onBadgeButtonClick, selectedLabel]
);
const { mutateAsync: attackDiscoveryBulk } = useAttackDiscoveryBulk();
const onSelectableChange = useCallback(
(newOptions: EuiSelectableOption[]) => setItems(newOptions),
[setItems]
async (newOptions: EuiSelectableOption[]) => {
setItems(newOptions);
if (isAttackDiscoveryAlert(attackDiscovery)) {
const visibility = newOptions[0].checked === 'on' ? 'not_shared' : 'shared';
await attackDiscoveryBulk({
attackDiscoveryAlertsEnabled,
ids: [attackDiscovery.id],
visibility,
});
}
},
[attackDiscovery, attackDiscoveryAlertsEnabled, attackDiscoveryBulk]
);
return (

View file

@ -17,7 +17,10 @@ describe('SummaryActions', () => {
beforeEach(() =>
render(
<TestProviders>
<SummaryActions attackDiscovery={mockAttackDiscovery} />
<SummaryActions
attackDiscovery={mockAttackDiscovery}
setSelectedAttackDiscoveries={jest.fn()}
/>
</TestProviders>
)
);

View file

@ -18,9 +18,14 @@ import * as i18n from './translations';
interface Props {
attackDiscovery: AttackDiscovery;
replacements?: Replacements;
setSelectedAttackDiscoveries: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
}
const SummaryActionsComponent: React.FC<Props> = ({ attackDiscovery, replacements }) => {
const SummaryActionsComponent: React.FC<Props> = ({
attackDiscovery,
replacements,
setSelectedAttackDiscoveries,
}) => {
const { euiTheme } = useEuiTheme();
const attackDiscoveries = useMemo(() => [attackDiscovery], [attackDiscovery]);
@ -116,7 +121,11 @@ const SummaryActionsComponent: React.FC<Props> = ({ attackDiscovery, replacement
<EuiFlexItem grow={false}>{nonInteractive}</EuiFlexItem>
<EuiFlexItem grow={false}>
<TakeAction attackDiscoveries={attackDiscoveries} replacements={replacements} />
<TakeAction
attackDiscoveries={attackDiscoveries}
replacements={replacements}
setSelectedAttackDiscoveries={setSelectedAttackDiscoveries}
/>
</EuiFlexItem>
</EuiFlexGroup>
);

View file

@ -141,6 +141,7 @@ const CurrentComponent: React.FC<Props> = ({
attackDiscovery={attackDiscovery}
isSelected={false}
initialIsOpen={getInitialIsOpen(i)}
setSelectedAttackDiscoveries={noop}
showAnonymized={showAnonymized}
replacements={selectedConnectorReplacements}
/>

View file

@ -30,16 +30,16 @@ import { SearchAndFilter } from './search_and_filter';
import { ACKNOWLEDGED, CLOSED, OPEN } from './search_and_filter/translations';
import { Summary } from '../summary';
import * as i18n from './translations';
import type { ConnectorFilterOptionData } from './types';
import { useDismissAttackDiscoveryGeneration } from '../../use_dismiss_attack_discovery_generations';
import { useIdsFromUrl } from './use_ids_from_url';
import { useFindAttackDiscoveries } from '../../use_find_attack_discoveries';
import { useGetAttackDiscoveryGenerations } from '../../use_get_attack_discovery_generations';
import { useKibanaFeatureFlags } from '../../use_kibana_feature_flags';
const DEFAULT_HISTORY_END = 'now';
const DEFAULT_HISTORY_START = 'now-24h';
const DEFAULT_PER_PAGE = 10;
const GET_ATTACK_DISCOVERY_GENERATIONS_SIZE = 50; // fetch up to 50 generations, with no filter by status
const ITEMS_PER_PAGE_OPTIONS = [10, 20, 50];
const EMPTY_ATTACK_DISCOVERY_ALERTS: AttackDiscoveryAlert[] = [];
@ -64,6 +64,7 @@ const HistoryComponent: React.FC<Props> = ({
onToggleShowAnonymized,
showAnonymized,
}) => {
const { attackDiscoveryAlertsEnabled } = useKibanaFeatureFlags();
const { assistantAvailability, http } = useAssistantContext();
const { ids: filterByAlertIds, setIdsUrl: setFilterByAlertIds } = useIdsFromUrl();
@ -96,6 +97,7 @@ const HistoryComponent: React.FC<Props> = ({
const oneBasedPageIndex = zeroBasedPageIndex + 1; // convert to one-based for API
setPage(oneBasedPageIndex);
setSelectedAttackDiscoveries({});
}, []);
const setIsSelected = useCallback(
@ -110,6 +112,7 @@ const HistoryComponent: React.FC<Props> = ({
const onChangeItemsPerPage = useCallback((pageSize: number) => {
setPage(FIRST_PAGE);
setPerPage(pageSize); // convert to zero-based for pagination component
setSelectedAttackDiscoveries({});
}, []);
const [statusItems, setStatusItems] = useState<EuiSelectableOption[]>([
@ -124,20 +127,13 @@ const HistoryComponent: React.FC<Props> = ({
.map((item) => item.label.toLowerCase());
}, [statusItems]);
const [connectorFilterItems, setConnectorFilterItems] = useState<
Array<EuiSelectableOption<ConnectorFilterOptionData>>
>([]);
const selectedConnectorNames: string[] = useMemo(
() => connectorFilterItems.filter((item) => item.checked).map((item) => item.label),
[connectorFilterItems]
);
const [selectedConnectorNames, setSelectedConnectorNames] = useState<string[]>([]);
const {
cancelRequest: cancelFindAttackDiscoveriesRequest,
data,
isLoading,
refetch,
refetch: refetchFindAttackDiscoveries,
} = useFindAttackDiscoveries({
connectorNames: selectedConnectorNames,
end: historyEnd,
@ -152,8 +148,6 @@ const HistoryComponent: React.FC<Props> = ({
status: selectedAlertWorkflowStatus,
});
const GET_ATTACK_DISCOVERY_GENERATIONS_SIZE = 50; // fetch up to 50 generations, with no filter by status
const {
cancelRequest: cancelGetAttackDiscoveryGenerations,
data: generationsData,
@ -178,7 +172,6 @@ const HistoryComponent: React.FC<Props> = ({
return () => {
clearInterval(intervalId);
cancelGetAttackDiscoveryGenerations();
cancelFindAttackDiscoveriesRequest();
};
}, [cancelFindAttackDiscoveriesRequest, cancelGetAttackDiscoveryGenerations, refetchGenerations]);
@ -186,33 +179,49 @@ const HistoryComponent: React.FC<Props> = ({
const pageCount = useMemo(() => Math.ceil((data?.total ?? 0) / perPage), [data, perPage]);
const { mutateAsync: dismissAttackDiscoveryGeneration } = useDismissAttackDiscoveryGeneration();
const onRefresh = useCallback(() => {
refetch();
const onRefresh = useCallback(async () => {
if (!attackDiscoveryAlertsEnabled) {
return;
}
refetchFindAttackDiscoveries();
// Dismiss all successful generations
// TODO: make this a bulk update:
generationsData?.generations
.filter(({ status }) => status === 'succeeded')
.forEach(({ execution_uuid: executionUuid }) => {
dismissAttackDiscoveryGeneration({ executionUuid }); // don't wait for this to finish
});
}, [dismissAttackDiscoveryGeneration, generationsData?.generations, refetch]);
if (generationsData?.generations) {
const dismissPromises = generationsData.generations
.filter(({ status }) => status === 'succeeded')
.map(({ execution_uuid: executionUuid }) =>
dismissAttackDiscoveryGeneration({ attackDiscoveryAlertsEnabled, executionUuid })
);
await Promise.all(dismissPromises);
}
setSelectedAttackDiscoveries({});
}, [
attackDiscoveryAlertsEnabled,
dismissAttackDiscoveryGeneration,
generationsData?.generations,
refetchFindAttackDiscoveries,
]);
return (
<>
<SearchAndFilter
aiConnectors={aiConnectors}
connectorFilterItems={connectorFilterItems}
connectorNames={connectorNames}
end={historyEnd}
filterByAlertIds={filterByAlertIds}
isLoading={isLoading}
onRefresh={onRefresh}
query={query}
setConnectorFilterItems={setConnectorFilterItems}
setEnd={setHistoryEnd}
setFilterByAlertIds={setFilterByAlertIds}
selectedConnectorNames={selectedConnectorNames}
setQuery={setQuery}
setSelectedAttackDiscoveries={setSelectedAttackDiscoveries}
setSelectedConnectorNames={setSelectedConnectorNames}
setShared={setShared}
setStart={setHistoryStart}
setStatusItems={setStatusItems}
@ -229,6 +238,7 @@ const HistoryComponent: React.FC<Props> = ({
isLoading={isLoading}
lastUpdated={null}
onToggleShowAnonymized={onToggleShowAnonymized}
refetchFindAttackDiscoveries={refetchFindAttackDiscoveries}
selectedAttackDiscoveries={selectedAttackDiscoveries}
selectedConnectorAttackDiscoveries={data?.data ?? EMPTY_ATTACK_DISCOVERY_ALERTS}
setSelectedAttackDiscoveries={setSelectedAttackDiscoveries}
@ -269,6 +279,7 @@ const HistoryComponent: React.FC<Props> = ({
replacements={attackDiscovery.replacements ?? EMPTY_REPLACEMENTS}
setIsSelected={setIsSelected}
isSelected={selectedAttackDiscoveries[attackDiscovery.id]}
setSelectedAttackDiscoveries={setSelectedAttackDiscoveries}
showAnonymized={showAnonymized}
/>
<EuiSpacer size="m" />

View file

@ -20,11 +20,12 @@ import {
} from '@elastic/eui';
import { css } from '@emotion/react';
import type { AIConnector } from '@kbn/elastic-assistant';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { getDescription } from './get_description';
import * as i18n from '../translations';
import type { ConnectorFilterOptionData } from '../../types';
import { useInvalidateFindAttackDiscoveries } from '../../../../use_find_attack_discoveries';
const LIST_PROPS = {
isVirtualized: false,
@ -33,21 +34,20 @@ const LIST_PROPS = {
interface Props {
aiConnectors: AIConnector[] | undefined;
connectorFilterItems: Array<EuiSelectableOption<ConnectorFilterOptionData>>;
connectorNames: string[] | undefined;
isLoading?: boolean;
setConnectorFilterItems: React.Dispatch<
React.SetStateAction<Array<EuiSelectableOption<ConnectorFilterOptionData>>>
>;
selectedConnectorNames: string[];
setSelectedConnectorNames: React.Dispatch<React.SetStateAction<string[]>>;
}
const ConnectorFilterComponent: React.FC<Props> = ({
aiConnectors,
connectorFilterItems,
connectorNames,
isLoading = false,
setConnectorFilterItems,
selectedConnectorNames,
setSelectedConnectorNames,
}) => {
const invalidateFindAttackDiscoveries = useInvalidateFindAttackDiscoveries();
const { euiTheme } = useEuiTheme();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
@ -63,16 +63,15 @@ const ConnectorFilterComponent: React.FC<Props> = ({
prefix: 'connectorFilterGroupPopover',
});
// re-populate the items when the connectors or names change
useEffect(() => {
if (aiConnectors != null && connectorNames != null) {
setConnectorFilterItems((prevItems) =>
connectorNames.map((name) => {
const connector = aiConnectors.find((x) => x.name === name);
const previousItem = prevItems.find((item) => item.key === name);
const connectorFilterItems: Array<EuiSelectableOption<ConnectorFilterOptionData>> =
useMemo(() => {
return (
connectorNames?.map((name) => {
const connector = aiConnectors?.find((x) => x.name === name);
const checked = selectedConnectorNames?.find((x) => x === name) !== undefined;
return {
checked: previousItem?.checked ?? undefined,
checked: checked ? 'on' : undefined,
data: {
description: getDescription(connector?.actionTypeId),
deleted: connector == null,
@ -80,10 +79,9 @@ const ConnectorFilterComponent: React.FC<Props> = ({
label: name,
key: name,
};
})
}) ?? []
);
}
}, [aiConnectors, connectorNames, setConnectorFilterItems]);
}, [aiConnectors, connectorNames, selectedConnectorNames]);
const renderOption = useCallback(
(option: EuiSelectableOption<ConnectorFilterOptionData>) => (
@ -118,13 +116,10 @@ const ConnectorFilterComponent: React.FC<Props> = ({
<EuiFlexItem grow={true}>
<EuiText
color={option.deleted ? euiTheme.colors.textSubdued : undefined}
css={css`
font-style: ${option.deleted ? 'italic' : 'normal'};
`}
data-test-subj="optionDescription"
size="s"
>
{option.deleted ? i18n.DELETED : option.description}
{option.deleted ? '-' : option.description}
</EuiText>
</EuiFlexItem>
@ -173,9 +168,14 @@ const ConnectorFilterComponent: React.FC<Props> = ({
const onSelectableChange = useCallback(
(newOptions: EuiSelectableOption[]) => {
setConnectorFilterItems(newOptions);
const newSelectedConnectorNames = newOptions
.filter((option) => option.checked === 'on')
.map((option) => option.label);
setSelectedConnectorNames(newSelectedConnectorNames);
invalidateFindAttackDiscoveries();
},
[setConnectorFilterItems]
[invalidateFindAttackDiscoveries, setSelectedConnectorNames]
);
return (

View file

@ -30,8 +30,9 @@ import { ConnectorFilter } from './connector_filter';
import { getCommonTimeRanges } from '../../../settings_flyout/alert_selection/helpers/get_common_time_ranges';
import { StatusFilter } from './status_filter';
import * as i18n from './translations';
import type { ConnectorFilterOptionData } from '../types';
import { VisibilityFilter } from './visibility_filter';
import { useInvalidateGetAttackDiscoveryGenerations } from '../../../use_get_attack_discovery_generations';
import { useInvalidateFindAttackDiscoveries } from '../../../use_find_attack_discoveries';
const updateButtonProps: EuiSuperUpdateButtonProps = {
fill: false,
@ -49,19 +50,18 @@ const box = {
interface Props {
aiConnectors: AIConnector[] | undefined;
connectorFilterItems: Array<EuiSelectableOption<ConnectorFilterOptionData>>;
connectorNames: string[] | undefined;
end: string | undefined;
filterByAlertIds: string[];
isLoading?: boolean;
onRefresh: () => void;
query: string | undefined;
setConnectorFilterItems: React.Dispatch<
React.SetStateAction<Array<EuiSelectableOption<ConnectorFilterOptionData>>>
>;
selectedConnectorNames: string[];
setEnd: React.Dispatch<React.SetStateAction<string | undefined>>;
setFilterByAlertIds: (ids: string[]) => void;
setQuery: React.Dispatch<React.SetStateAction<string | undefined>>;
setSelectedAttackDiscoveries: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
setSelectedConnectorNames: React.Dispatch<React.SetStateAction<string[]>>;
setShared: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setStart: React.Dispatch<React.SetStateAction<string | undefined>>;
setStatusItems: React.Dispatch<React.SetStateAction<EuiSelectableOption[]>>;
@ -75,17 +75,18 @@ interface Props {
const SearchAndFilterComponent: React.FC<Props> = ({
aiConnectors,
connectorFilterItems,
connectorNames,
end,
filterByAlertIds,
isLoading = false,
onRefresh,
query,
setConnectorFilterItems,
selectedConnectorNames,
setEnd,
setFilterByAlertIds,
setQuery,
setSelectedAttackDiscoveries,
setSelectedConnectorNames,
setShared,
setStart,
setStatusItems,
@ -93,6 +94,8 @@ const SearchAndFilterComponent: React.FC<Props> = ({
start,
statusItems,
}) => {
const invalidateGetAttackDiscoveryGenerations = useInvalidateGetAttackDiscoveryGenerations();
const invalidateFindAttackDiscoveries = useInvalidateFindAttackDiscoveries();
const { euiTheme } = useEuiTheme();
// Users accumulate an "unsubmitted" query as they type in the search bar,
@ -125,8 +128,17 @@ const SearchAndFilterComponent: React.FC<Props> = ({
setStart(startDate);
setEnd(endDate);
invalidateFindAttackDiscoveries();
invalidateGetAttackDiscoveryGenerations();
},
[setEnd, setQuery, setStart, unSubmittedQuery]
[
invalidateFindAttackDiscoveries,
invalidateGetAttackDiscoveryGenerations,
setEnd,
setQuery,
setStart,
unSubmittedQuery,
]
);
/**
@ -140,8 +152,18 @@ const SearchAndFilterComponent: React.FC<Props> = ({
const localOnRefresh = useCallback(() => {
setQuery(unSubmittedQuery);
setSelectedAttackDiscoveries({});
onRefresh();
}, [onRefresh, setQuery, unSubmittedQuery]);
invalidateFindAttackDiscoveries();
invalidateGetAttackDiscoveryGenerations();
}, [
invalidateFindAttackDiscoveries,
invalidateGetAttackDiscoveryGenerations,
onRefresh,
setQuery,
setSelectedAttackDiscoveries,
unSubmittedQuery,
]);
const onKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
@ -153,8 +175,11 @@ const SearchAndFilterComponent: React.FC<Props> = ({
);
const removeAlertIdFromFilter = useCallback(
(id: string) => setFilterByAlertIds(filterByAlertIds.filter((alertId) => alertId !== id)),
[filterByAlertIds, setFilterByAlertIds]
(id: string) => {
setFilterByAlertIds(filterByAlertIds.filter((alertId) => alertId !== id));
invalidateFindAttackDiscoveries();
},
[filterByAlertIds, invalidateFindAttackDiscoveries, setFilterByAlertIds]
);
return (
@ -193,10 +218,10 @@ const SearchAndFilterComponent: React.FC<Props> = ({
<EuiFlexItem grow={false}>
<ConnectorFilter
aiConnectors={aiConnectors}
connectorFilterItems={connectorFilterItems}
connectorNames={connectorNames}
isLoading={isLoading}
setConnectorFilterItems={setConnectorFilterItems}
selectedConnectorNames={selectedConnectorNames}
setSelectedConnectorNames={setSelectedConnectorNames}
/>
</EuiFlexItem>

View file

@ -17,6 +17,7 @@ import { css } from '@emotion/react';
import React, { useCallback, useMemo, useState } from 'react';
import * as i18n from '../translations';
import { useInvalidateFindAttackDiscoveries } from '../../../../use_find_attack_discoveries';
interface Props {
isLoading?: boolean;
@ -29,6 +30,7 @@ const StatusFilterComponent: React.FC<Props> = ({
setStatusItems,
statusItems,
}) => {
const invalidateFindAttackDiscoveries = useInvalidateFindAttackDiscoveries();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const onFilterButtonClick = useCallback(() => {
@ -62,8 +64,11 @@ const StatusFilterComponent: React.FC<Props> = ({
);
const onSelectableChange = useCallback(
(newOptions: EuiSelectableOption[]) => setStatusItems(newOptions),
[setStatusItems]
(newOptions: EuiSelectableOption[]) => {
setStatusItems(newOptions);
invalidateFindAttackDiscoveries();
},
[invalidateFindAttackDiscoveries, setStatusItems]
);
return (

View file

@ -20,6 +20,7 @@ import { css } from '@emotion/react';
import React, { useCallback, useMemo, useState } from 'react';
import * as i18n from '../translations';
import { useInvalidateFindAttackDiscoveries } from '../../../../use_find_attack_discoveries';
const LIST_PROPS = {
isVirtualized: false,
@ -43,6 +44,8 @@ interface Props {
}
const VisibilityFilterComponent: React.FC<Props> = ({ isLoading = false, setShared, shared }) => {
const invalidateFindAttackDiscoveries = useInvalidateFindAttackDiscoveries();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const onFilterButtonClick = useCallback(() => {
@ -148,8 +151,9 @@ const VisibilityFilterComponent: React.FC<Props> = ({ isLoading = false, setShar
}
setItems(newOptions);
invalidateFindAttackDiscoveries();
},
[setShared]
[invalidateFindAttackDiscoveries, setShared]
);
return (

View file

@ -27,6 +27,7 @@ interface Props {
isLoading?: boolean;
lastUpdated: Date | null;
onToggleShowAnonymized: () => void;
refetchFindAttackDiscoveries?: () => void;
selectedAttackDiscoveries: Record<string, boolean>;
selectedConnectorAttackDiscoveries: AttackDiscoveryAlert[];
setSelectedAttackDiscoveries: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
@ -39,6 +40,7 @@ const SummaryComponent: React.FC<Props> = ({
isLoading = false,
lastUpdated,
onToggleShowAnonymized,
refetchFindAttackDiscoveries,
selectedAttackDiscoveries,
selectedConnectorAttackDiscoveries,
setSelectedAttackDiscoveries,
@ -66,6 +68,7 @@ const SummaryComponent: React.FC<Props> = ({
<EuiFlexItem grow={false}>
<SelectedActions
refetchFindAttackDiscoveries={refetchFindAttackDiscoveries}
selectedConnectorAttackDiscoveries={selectedConnectorAttackDiscoveries}
selectedAttackDiscoveries={selectedAttackDiscoveries}
setSelectedAttackDiscoveries={setSelectedAttackDiscoveries}

View file

@ -14,12 +14,14 @@ import { TakeAction } from '../../take_action';
import * as i18n from './translations';
interface Props {
refetchFindAttackDiscoveries?: () => void;
selectedAttackDiscoveries: Record<string, boolean>;
selectedConnectorAttackDiscoveries: AttackDiscoveryAlert[];
setSelectedAttackDiscoveries: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
}
const SelectedActionsComponent: React.FC<Props> = ({
refetchFindAttackDiscoveries,
selectedAttackDiscoveries,
selectedConnectorAttackDiscoveries,
setSelectedAttackDiscoveries,
@ -58,7 +60,13 @@ const SelectedActionsComponent: React.FC<Props> = ({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<TakeAction attackDiscoveries={selected} buttonSize="xs" buttonText={buttonText} />
<TakeAction
attackDiscoveries={selected}
buttonSize="xs"
buttonText={buttonText}
refetchFindAttackDiscoveries={refetchFindAttackDiscoveries}
setSelectedAttackDiscoveries={setSelectedAttackDiscoveries}
/>
</EuiFlexItem>
</EuiFlexGroup>
);

View file

@ -18,7 +18,10 @@ describe('TakeAction', () => {
render(
<TestProviders>
<TakeAction attackDiscoveries={[mockAttackDiscovery]} />
<TakeAction
attackDiscoveries={[mockAttackDiscovery]}
setSelectedAttackDiscoveries={jest.fn()}
/>
</TestProviders>
);

View file

@ -26,20 +26,26 @@ import { useViewInAiAssistant } from '../attack_discovery_panel/view_in_ai_assis
import { APP_ID } from '../../../../../common';
import { useKibana } from '../../../../common/lib/kibana';
import * as i18n from './translations';
import { useAttackDiscoveryBulk } from '../../use_attack_discovery_bulk';
import { useKibanaFeatureFlags } from '../../use_kibana_feature_flags';
import { isAttackDiscoveryAlert } from '../../utils/is_attack_discovery_alert';
interface Props {
attackDiscoveries: AttackDiscovery[] | AttackDiscoveryAlert[];
buttonText?: string;
buttonSize?: 's' | 'xs';
refetchFindAttackDiscoveries?: () => void;
replacements?: Replacements;
setSelectedAttackDiscoveries: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
}
const TakeActionComponent: React.FC<Props> = ({
attackDiscoveries,
buttonSize = 's',
buttonText,
refetchFindAttackDiscoveries,
replacements,
setSelectedAttackDiscoveries,
}) => {
const {
services: { cases },
@ -86,8 +92,78 @@ const TakeActionComponent: React.FC<Props> = ({
[attackDiscoveries]
);
const attackDiscoveryIds: string[] = useMemo(
() =>
attackDiscoveries.flatMap((attackDiscovery) =>
attackDiscovery.id != null ? [attackDiscovery.id] : []
),
[attackDiscoveries]
);
const { mutateAsync: attackDiscoveryBulk } = useAttackDiscoveryBulk();
// click handlers for the popover actions:
const onClickAddToNewCase = useCallback(() => {
const onClickMarkAsAcknowledged = useCallback(async () => {
closePopover();
await attackDiscoveryBulk({
attackDiscoveryAlertsEnabled,
ids: attackDiscoveryIds,
kibanaAlertWorkflowStatus: 'acknowledged',
});
setSelectedAttackDiscoveries({});
refetchFindAttackDiscoveries?.();
}, [
attackDiscoveryAlertsEnabled,
attackDiscoveryBulk,
attackDiscoveryIds,
closePopover,
refetchFindAttackDiscoveries,
setSelectedAttackDiscoveries,
]);
const onClickMarkAsClosed = useCallback(async () => {
closePopover();
await attackDiscoveryBulk({
attackDiscoveryAlertsEnabled,
ids: attackDiscoveryIds,
kibanaAlertWorkflowStatus: 'closed',
});
refetchFindAttackDiscoveries?.();
setSelectedAttackDiscoveries({});
}, [
attackDiscoveryAlertsEnabled,
attackDiscoveryBulk,
attackDiscoveryIds,
closePopover,
refetchFindAttackDiscoveries,
setSelectedAttackDiscoveries,
]);
const onClickMarkAsOpen = useCallback(async () => {
closePopover();
await attackDiscoveryBulk({
attackDiscoveryAlertsEnabled,
ids: attackDiscoveryIds,
kibanaAlertWorkflowStatus: 'open',
});
setSelectedAttackDiscoveries({});
refetchFindAttackDiscoveries?.();
}, [
attackDiscoveryAlertsEnabled,
attackDiscoveryBulk,
attackDiscoveryIds,
closePopover,
refetchFindAttackDiscoveries,
setSelectedAttackDiscoveries,
]);
const onClickAddToNewCase = useCallback(async () => {
closePopover();
onAddToNewCase({
@ -95,7 +171,16 @@ const TakeActionComponent: React.FC<Props> = ({
markdownComments: [markdown],
replacements,
});
}, [closePopover, onAddToNewCase, alertIds, markdown, replacements]);
await refetchFindAttackDiscoveries?.();
}, [
closePopover,
onAddToNewCase,
alertIds,
markdown,
replacements,
refetchFindAttackDiscoveries,
]);
const onClickAddToExistingCase = useCallback(() => {
closePopover();
@ -178,31 +263,64 @@ const TakeActionComponent: React.FC<Props> = ({
]
);
const allItems = useMemo(
() =>
attackDiscoveryAlertsEnabled
? [
<EuiContextMenuItem
data-test-subj="markAsAcknowledged"
disabled={true}
key="markAsAcknowledged"
onClick={() => {}}
>
{i18n.MARK_AS_ACKNOWLEDGED}
</EuiContextMenuItem>,
<EuiContextMenuItem
data-test-subj="markAsClosed"
disabled={true}
key="markAsClosed"
onClick={() => {}}
>
{i18n.MARK_AS_CLOSED}
</EuiContextMenuItem>,
...items,
]
: items,
[attackDiscoveryAlertsEnabled, items]
);
const allItems = useMemo(() => {
if (!attackDiscoveryAlertsEnabled) {
return items;
}
const isSingleAttackDiscovery = attackDiscoveries.length === 1;
const firstAttackDiscovery = isSingleAttackDiscovery ? attackDiscoveries[0] : null;
const isAlert = firstAttackDiscovery && isAttackDiscoveryAlert(firstAttackDiscovery);
const isOpen = isAlert && firstAttackDiscovery.alertWorkflowStatus === 'open';
const isAcknowledged = isAlert && firstAttackDiscovery.alertWorkflowStatus === 'acknowledged';
const isClosed = isAlert && firstAttackDiscovery.alertWorkflowStatus === 'closed';
const markAsOpenItem = !isOpen
? [
<EuiContextMenuItem
data-test-subj="markAsOpen"
key="markAsOpen"
onClick={onClickMarkAsOpen}
>
{i18n.MARK_AS_OPEN}
</EuiContextMenuItem>,
]
: [];
const markAsAcknowledgedItem = !isAcknowledged
? [
<EuiContextMenuItem
data-test-subj="markAsAcknowledged"
key="markAsAcknowledged"
onClick={onClickMarkAsAcknowledged}
>
{i18n.MARK_AS_ACKNOWLEDGED}
</EuiContextMenuItem>,
]
: [];
const markAsClosedItem = !isClosed
? [
<EuiContextMenuItem
data-test-subj="markAsClosed"
key="markAsClosed"
onClick={onClickMarkAsClosed}
>
{i18n.MARK_AS_CLOSED}
</EuiContextMenuItem>,
]
: [];
return [...markAsOpenItem, ...markAsAcknowledgedItem, ...markAsClosedItem, ...items].flat();
}, [
attackDiscoveries,
attackDiscoveryAlertsEnabled,
items,
onClickMarkAsAcknowledged,
onClickMarkAsClosed,
onClickMarkAsOpen,
]);
return (
<EuiPopover

View file

@ -35,6 +35,13 @@ export const MARK_AS_CLOSED = i18n.translate(
}
);
export const MARK_AS_OPEN = i18n.translate(
'xpack.securitySolution.attackDiscovery.attackDiscoveryPanel.actions.takeAction.markAsOpenButtonLabel',
{
defaultMessage: 'Mark as open',
}
);
export const TAKE_ACTION = i18n.translate(
'xpack.securitySolution.attackDiscovery.attackDiscoveryPanel.actions.takeAction.title',
{

View file

@ -15,6 +15,7 @@ import { usePollApi } from './use_poll_api/use_poll_api';
import { useAttackDiscovery } from '.';
import { ERROR_GENERATING_ATTACK_DISCOVERIES } from '../translations';
import { useKibana as mockUseKibana } from '../../../common/lib/kibana/__mocks__';
import { createQueryWrapperMock } from '../../../common/__mocks__/query_wrapper';
import { useKibanaFeatureFlags } from '../use_kibana_feature_flags';
jest.mock('../use_kibana_feature_flags');
@ -111,6 +112,8 @@ const setStatus = jest.fn();
const SIZE = 20;
const { wrapper: queryWrapper } = createQueryWrapperMock();
describe('useAttackDiscovery', () => {
const mockPollApi = {
cancelAttackDiscovery: jest.fn(),
@ -133,12 +136,16 @@ describe('useAttackDiscovery', () => {
});
it('initializes with correct default values', () => {
const { result } = renderHook(() =>
useAttackDiscovery({
connectorId: 'test-id',
setLoadingConnectorId,
size: 20,
})
const { result } = renderHook(
() =>
useAttackDiscovery({
connectorId: 'test-id',
setLoadingConnectorId,
size: 20,
}),
{
wrapper: queryWrapper,
}
);
expect(result.current.alertsContextCount).toBeNull();
@ -156,7 +163,12 @@ describe('useAttackDiscovery', () => {
it('fetches attack discoveries and updates state correctly', async () => {
(mockedUseKibana.services.http.fetch as jest.Mock).mockResolvedValue(mockAttackDiscoveryPost);
const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE }));
const { result } = renderHook(
() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE }),
{
wrapper: queryWrapper,
}
);
await act(async () => {
await result.current.fetchAttackDiscoveries();
@ -179,7 +191,12 @@ describe('useAttackDiscovery', () => {
const error = new Error(errorMessage);
(mockedUseKibana.services.http.post as jest.Mock).mockRejectedValue(error);
const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE }));
const { result } = renderHook(
() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE }),
{
wrapper: queryWrapper,
}
);
await act(async () => {
await result.current.fetchAttackDiscoveries();
@ -195,12 +212,16 @@ describe('useAttackDiscovery', () => {
it('sets loading state based on poll status', async () => {
(usePollApi as jest.Mock).mockReturnValue({ ...mockPollApi, status: 'running' });
const { result } = renderHook(() =>
useAttackDiscovery({
connectorId: 'test-id',
setLoadingConnectorId,
size: SIZE,
})
const { result } = renderHook(
() =>
useAttackDiscovery({
connectorId: 'test-id',
setLoadingConnectorId,
size: SIZE,
}),
{
wrapper: queryWrapper,
}
);
expect(result.current.isLoading).toBe(true);
@ -218,7 +239,12 @@ describe('useAttackDiscovery', () => {
},
status: 'succeeded',
});
const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE }));
const { result } = renderHook(
() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE }),
{
wrapper: queryWrapper,
}
);
expect(result.current.alertsContextCount).toEqual(20);
// this is set from usePollApi
@ -243,7 +269,12 @@ describe('useAttackDiscovery', () => {
},
status: 'failed',
});
const { result } = renderHook(() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE }));
const { result } = renderHook(
() => useAttackDiscovery({ connectorId: 'test-id', size: SIZE }),
{
wrapper: queryWrapper,
}
);
expect(result.current.failureReason).toEqual('something bad');
expect(result.current.isLoading).toBe(false);
@ -257,12 +288,16 @@ describe('useAttackDiscovery', () => {
data: [], // <-- zero connectors configured
});
renderHook(() =>
useAttackDiscovery({
connectorId: 'test-id',
setLoadingConnectorId,
size: SIZE,
})
renderHook(
() =>
useAttackDiscovery({
connectorId: 'test-id',
setLoadingConnectorId,
size: SIZE,
}),
{
wrapper: queryWrapper,
}
);
});

View file

@ -27,6 +27,7 @@ import { getErrorToastText } from '../helpers';
import { getGenAiConfig, getRequestBody } from './helpers';
import { CONNECTOR_ERROR, ERROR_GENERATING_ATTACK_DISCOVERIES } from '../translations';
import * as i18n from './translations';
import { useInvalidateGetAttackDiscoveryGenerations } from '../use_get_attack_discovery_generations';
import { useKibanaFeatureFlags } from '../use_kibana_feature_flags';
interface FetchAttackDiscoveriesOptions {
@ -180,6 +181,8 @@ export const useAttackDiscovery = ({
}
}, [attackDiscoveryAlertsEnabled, connectorId, pollData]);
const invalidateGetAttackDiscoveryGenerations = useInvalidateGetAttackDiscoveryGenerations();
/** The callback when users click the Generate button */
const fetchAttackDiscoveries = useCallback(
async (options: FetchAttackDiscoveriesOptions | undefined) => {
@ -239,6 +242,8 @@ export const useAttackDiscovery = ({
title: ERROR_GENERATING_ATTACK_DISCOVERIES,
text: getErrorToastText(error),
});
} finally {
invalidateGetAttackDiscoveryGenerations();
}
},
[
@ -246,6 +251,7 @@ export const useAttackDiscovery = ({
connectorId,
connectorName,
http,
invalidateGetAttackDiscoveryGenerations,
requestBody,
setLoadingConnectorId,
setPollStatus,

View file

@ -0,0 +1,89 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type {
PostAttackDiscoveryBulkRequestBody,
PostAttackDiscoveryBulkResponse,
} from '@kbn/elastic-assistant-common';
import { ATTACK_DISCOVERY_BULK } from '@kbn/elastic-assistant-common';
import { useMutation } from '@tanstack/react-query';
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
import { KibanaServices } from '../../../common/lib/kibana';
import * as i18n from './translations';
import { useInvalidateFindAttackDiscoveries } from '../use_find_attack_discoveries';
import { useKibanaFeatureFlags } from '../use_kibana_feature_flags';
export const ATTACK_DISCOVERY_BULK_MUTATION_KEY = ['POST', ATTACK_DISCOVERY_BULK];
interface AttackDiscoveryBulkParams {
attackDiscoveryAlertsEnabled: boolean;
ids: string[];
kibanaAlertWorkflowStatus?: 'acknowledged' | 'closed' | 'open';
visibility?: 'not_shared' | 'shared';
/** Optional AbortSignal for cancelling request */
signal?: AbortSignal;
}
/** Disables the attack discovery schedule. */
const attackDiscoveryBulk = async ({
attackDiscoveryAlertsEnabled,
ids,
kibanaAlertWorkflowStatus,
signal,
visibility,
}: AttackDiscoveryBulkParams): Promise<PostAttackDiscoveryBulkResponse> => {
const body: PostAttackDiscoveryBulkRequestBody = {
update: {
ids,
kibana_alert_workflow_status: kibanaAlertWorkflowStatus,
visibility,
},
};
if (!attackDiscoveryAlertsEnabled) {
return {
data: [],
};
}
return KibanaServices.get().http.post<PostAttackDiscoveryBulkResponse>(ATTACK_DISCOVERY_BULK, {
body: JSON.stringify(body, null, 2),
signal,
version: '1',
});
};
export const useAttackDiscoveryBulk = () => {
const { attackDiscoveryAlertsEnabled } = useKibanaFeatureFlags();
const { addError, addSuccess } = useAppToasts();
const invalidateFindAttackDiscoveries = useInvalidateFindAttackDiscoveries();
return useMutation<PostAttackDiscoveryBulkResponse, Error, AttackDiscoveryBulkParams>(
({ ids, kibanaAlertWorkflowStatus, visibility }) =>
attackDiscoveryBulk({
attackDiscoveryAlertsEnabled,
ids,
kibanaAlertWorkflowStatus,
visibility,
}),
{
mutationKey: ATTACK_DISCOVERY_BULK_MUTATION_KEY,
onSuccess: () => {
if (attackDiscoveryAlertsEnabled) {
invalidateFindAttackDiscoveries();
addSuccess(i18n.ATTACK_DISCOVERIES_SUCCESSFULLY_UPDATED);
}
},
onError: (error) => {
if (attackDiscoveryAlertsEnabled) {
addError(error, { title: i18n.ERROR_UPDATING_ATTACK_DISCOVERIES });
}
},
}
);
};

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
export const ATTACK_DISCOVERIES_SUCCESSFULLY_UPDATED = i18n.translate(
'xpack.securitySolution.attackDiscovery.useAttackDiscoveryBulk.attackDiscoveriesSuccessfullyUpdatedToast',
{
defaultMessage: 'Attack discoveries successfully updated',
}
);
export const ERROR_UPDATING_ATTACK_DISCOVERIES = i18n.translate(
'xpack.securitySolution.attackDiscovery.useAttackDiscoveryBulk.errorUpdatingAttackDiscoveriesErrorToast',
{
defaultMessage: 'Error updating Attack discoveries',
}
);

View file

@ -14,6 +14,7 @@ import { useAppToasts } from '../../../common/hooks/use_app_toasts';
import { KibanaServices } from '../../../common/lib/kibana';
import * as i18n from './translations';
import { useInvalidateGetAttackDiscoveryGenerations } from '../use_get_attack_discovery_generations';
import { useKibanaFeatureFlags } from '../use_kibana_feature_flags';
export const DISMISS_ATTACK_DISCOVERY_GENERATION_MUTATION_KEY = [
'POST',
@ -21,15 +22,28 @@ export const DISMISS_ATTACK_DISCOVERY_GENERATION_MUTATION_KEY = [
];
interface DismissAttackDiscoveryGenerationParams {
attackDiscoveryAlertsEnabled: boolean;
executionUuid: string;
/** Optional AbortSignal for cancelling request */
signal?: AbortSignal;
}
/** Disables the attack discovery schedule. */
const dismissAttackDiscoveryGeneration = async ({
attackDiscoveryAlertsEnabled,
executionUuid,
signal,
}: DismissAttackDiscoveryGenerationParams): Promise<PostAttackDiscoveryGenerationsDismissResponse> => {
if (!attackDiscoveryAlertsEnabled) {
return {
connector_id: '',
status: 'dismissed',
discoveries: 0,
execution_uuid: executionUuid,
loading_message: '',
start: new Date().toISOString(),
};
}
return KibanaServices.get().http.post<PostAttackDiscoveryGenerationsDismissResponse>(
replaceParams(ATTACK_DISCOVERY_GENERATIONS_BY_ID_DISMISS, { execution_uuid: executionUuid }),
{ version: '1', signal }
@ -37,6 +51,7 @@ const dismissAttackDiscoveryGeneration = async ({
};
export const useDismissAttackDiscoveryGeneration = () => {
const { attackDiscoveryAlertsEnabled } = useKibanaFeatureFlags();
const { addError } = useAppToasts();
const invalidateGetAttackDiscoveryGenerations = useInvalidateGetAttackDiscoveryGenerations();
@ -45,13 +60,21 @@ export const useDismissAttackDiscoveryGeneration = () => {
PostAttackDiscoveryGenerationsDismissResponse,
Error,
DismissAttackDiscoveryGenerationParams
>(({ executionUuid }) => dismissAttackDiscoveryGeneration({ executionUuid }), {
mutationKey: DISMISS_ATTACK_DISCOVERY_GENERATION_MUTATION_KEY,
onSuccess: () => {
invalidateGetAttackDiscoveryGenerations();
},
onError: (error) => {
addError(error, { title: i18n.DISMISS_ATTACK_DISCOVERY_GENERATIONS_FAILURE() });
},
});
>(
({ executionUuid }) =>
dismissAttackDiscoveryGeneration({ attackDiscoveryAlertsEnabled, executionUuid }),
{
mutationKey: DISMISS_ATTACK_DISCOVERY_GENERATION_MUTATION_KEY,
onSuccess: () => {
if (attackDiscoveryAlertsEnabled) {
invalidateGetAttackDiscoveryGenerations();
}
},
onError: (error) => {
if (attackDiscoveryAlertsEnabled) {
addError(error, { title: i18n.DISMISS_ATTACK_DISCOVERY_GENERATIONS_FAILURE() });
}
},
}
);
};

View file

@ -13,8 +13,9 @@ import type {
RefetchOptions,
RefetchQueryFilters,
} from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback, useRef } from 'react';
import { useKibanaFeatureFlags } from '../use_kibana_feature_flags';
interface Props {
ids?: string[];
@ -68,6 +69,7 @@ export const useFindAttackDiscoveries = ({
sortField = '@timestamp',
sortOrder = 'desc',
}: Props): UseFindAttackDiscoveries => {
const { attackDiscoveryAlertsEnabled } = useKibanaFeatureFlags();
const abortController = useRef(new AbortController());
const cancelRequest = useCallback(() => {
@ -76,8 +78,8 @@ export const useFindAttackDiscoveries = ({
}, []);
const queryFn = useCallback(
async ({ pageParam }: { pageParam?: PageParam }) => {
return http.fetch<AttackDiscoveryFindResponse>(ATTACK_DISCOVERY_FIND, {
async ({ pageParam }: { pageParam?: PageParam }) =>
http.fetch<AttackDiscoveryFindResponse>(ATTACK_DISCOVERY_FIND, {
method: 'GET',
version: API_VERSIONS.internal.v1,
query: {
@ -94,8 +96,7 @@ export const useFindAttackDiscoveries = ({
status,
},
signal: abortController.current.signal,
});
},
}),
[
connectorNames,
end,
@ -113,7 +114,10 @@ export const useFindAttackDiscoveries = ({
);
const getNextPageParam = useCallback((lastPage: AttackDiscoveryFindResponse) => {
const totalPages = Math.max(DEFAULT_PAGE, Math.ceil(lastPage.total / lastPage.perPage));
const totalPages = Math.max(
DEFAULT_PAGE,
Math.ceil(lastPage.total / (lastPage.per_page ?? DEFAULT_PER_PAGE))
);
if (totalPages === lastPage.page) {
return;
@ -150,7 +154,7 @@ export const useFindAttackDiscoveries = ({
],
queryFn,
{
enabled: isAssistantEnabled,
enabled: isAssistantEnabled && attackDiscoveryAlertsEnabled,
getNextPageParam,
refetchOnWindowFocus,
}
@ -165,3 +169,18 @@ export const useFindAttackDiscoveries = ({
status: queryStatus,
};
};
/**
* We use this hook to invalidate the attack discovery generations cache.
*
* @returns A attack discovery schedule cache invalidation callback
*/
export const useInvalidateFindAttackDiscoveries = () => {
const queryClient = useQueryClient();
return useCallback(() => {
queryClient.invalidateQueries(['GET', ATTACK_DISCOVERY_FIND], {
refetchType: 'all',
});
}, [queryClient]);
};

View file

@ -19,6 +19,8 @@ import type {
GetAttackDiscoveryGenerationsResponse,
} from '@kbn/elastic-assistant-common';
import { useKibanaFeatureFlags } from '../use_kibana_feature_flags';
interface Props extends GetAttackDiscoveryGenerationsRequestQuery {
http: HttpSetup;
isAssistantEnabled: boolean;
@ -44,6 +46,7 @@ export const useGetAttackDiscoveryGenerations = ({
start,
refetchOnWindowFocus = false,
}: Props): UseGetAttackDiscoveryGenerations => {
const { attackDiscoveryAlertsEnabled } = useKibanaFeatureFlags();
const abortController = useRef(new AbortController());
const cancelRequest = useCallback(() => {
@ -68,7 +71,7 @@ export const useGetAttackDiscoveryGenerations = ({
['GET', ATTACK_DISCOVERY_GENERATIONS, end, isAssistantEnabled, size, start],
queryFn,
{
enabled: isAssistantEnabled,
enabled: isAssistantEnabled && attackDiscoveryAlertsEnabled,
refetchOnWindowFocus,
}
);
@ -93,7 +96,7 @@ export const useInvalidateGetAttackDiscoveryGenerations = () => {
return useCallback(() => {
queryClient.invalidateQueries(['GET', ATTACK_DISCOVERY_GENERATIONS], {
refetchType: 'active',
refetchType: 'all',
});
}, [queryClient]);
};