From c209f3442d96f416f417ec4f67d633c68e10d314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= Date: Tue, 2 Jul 2024 00:09:18 +0200 Subject: [PATCH] [EDR Workflows][Process descendants filter] Transform event filter entry for filtering process descendant (#187141) ## Summary This PR adds logic to server side artifact logic, in order to transform Event Filter entries to the expected format, if process descendant filtering is enabled. Conditions: - feature flag is enabled: `xpack.securitySolution.enableExperimental.filterProcessDescendantsForEventFiltersEnabled` - amongst the `tags`, there is the `filter_process_descendants` tag Output: the output contains a new operator called `descendent_of`, with all the original entries listed as the operator's values, and one additional `event.category is process` entry. Example input entry (only relevant fields): ```json "entries": [ { "field": "process.args", "operator": "included", "type": "match", "value": "IGNOREMYCHILDREN" } ], "tags": [ "filter_process_descendants" ], ``` Example output entry: ```json { "entries": [ { "type": "simple", "entries": [ { "operator": "included", "type": "descendent_of", // note: writing descendEnt here to harmonise with endpoint implementation "value": { "entries": [ { "type": "simple", "entries": [ { "field": "process.args", "operator": "included", "type": "exact_cased", "value": "IGNOREMYCHILDREN" }, { "field": "event.category", "operator": "included", "type": "exact_cased", "value": "process" } ] } ] } } ] } ] } ``` ### e2e testing - spin up Kibana with the FF enabled - spin up a v15-snapshot Agent with Endpoint integration on VM, enroll it - create an Event Filter with the following setup, where '_Process descendants_' is selected and `process.args` is `IGNOREMYCHILDREN`: image - create a `test.sh` on your VM with the following content: ```sh echo "Creating file test_file_$1.tmp" touch test_file_$1.tmp # recursively call the original script if [ $1 -ge 1 ] then sh test.sh $(( $1 - 1 )) $2 fi ``` (alternatively you can use the python script from the endpoint tests, https://github.com/elastic/endpoint-dev/blob/790c611325998c6ef8578144844d9260b8f73044/Python/endpoint/test/test_event_filtering.py#L439-L450) - run the script `sh test.sh 6 DO_NOT_IGNOREMYCHILDREN` image - go to Security -> Explore -> Hosts to see the reported events - I added some filters to easily see the events image - you should see that all events are there image - now try with `sh test.sh 9 IGNOREMYCHILDREN` image - now only the events of the top process are visible, the events from descendants are not image ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../endpoint/service/artifacts/constants.ts | 17 ++ .../endpoint/service/artifacts/utils.ts | 4 +- .../event_filters/view/components/form.tsx | 12 +- .../endpoint/lib/artifacts/lists.test.ts | 269 ++++++++++++++++-- .../server/endpoint/lib/artifacts/lists.ts | 83 ++++-- .../endpoint/schemas/artifacts/lists.ts | 25 ++ .../manifest_manager/manifest_manager.test.ts | 85 ++++-- .../manifest_manager/manifest_manager.ts | 2 +- 8 files changed, 409 insertions(+), 88 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts index 362156ce3b5f..9917742d7b7c 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts @@ -5,7 +5,9 @@ * 2.0. */ +import type { EntryMatch } from '@kbn/securitysolution-io-ts-list-types'; import { ENDPOINT_ARTIFACT_LIST_IDS } from '@kbn/securitysolution-list-constants'; +import { EVENT_FILTERS_OPERATORS } from '@kbn/securitysolution-list-utils'; export const BY_POLICY_ARTIFACT_TAG_PREFIX = 'policy:'; @@ -13,6 +15,21 @@ export const GLOBAL_ARTIFACT_TAG = `${BY_POLICY_ARTIFACT_TAG_PREFIX}all`; export const FILTER_PROCESS_DESCENDANTS_TAG = 'filter_process_descendants'; +export const PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY: EntryMatch = Object.freeze({ + field: 'event.category', + operator: 'included', + type: 'match', + value: 'process', +}); + +export const PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY_TEXT: string = `${ + PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY.field +} ${ + EVENT_FILTERS_OPERATORS.find( + ({ type }) => type === PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY.type + )?.message +} ${PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY.value}`; + // TODO: refact all uses of `ALL_ENDPOINT_ARTIFACTS_LIST_IDS to sue new const from shared package export const ALL_ENDPOINT_ARTIFACT_LIST_IDS = ENDPOINT_ARTIFACT_LIST_IDS; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts index 50b4861a345c..322cd87fd7be 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts @@ -23,7 +23,7 @@ export type TagFilter = (tag: string) => boolean; const POLICY_ID_START_POSITION = BY_POLICY_ARTIFACT_TAG_PREFIX.length; export const isArtifactGlobal = (item: Partial>): boolean => { - return (item.tags ?? []).find((tag) => tag === GLOBAL_ARTIFACT_TAG) !== undefined; + return (item.tags ?? []).includes(GLOBAL_ARTIFACT_TAG); }; export const isArtifactByPolicy = (item: Pick): boolean => { @@ -96,7 +96,7 @@ export const getEffectedPolicySelectionByTags = ( export const isFilterProcessDescendantsEnabled = ( item: Partial> -): boolean => (item.tags ?? []).find((tag) => tag === FILTER_PROCESS_DESCENDANTS_TAG) !== undefined; +): boolean => (item.tags ?? []).includes(FILTER_PROCESS_DESCENDANTS_TAG); export const isFilterProcessDescendantsTag: TagFilter = (tag) => tag === FILTER_PROCESS_DESCENDANTS_TAG; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx index 2413007fd693..ecb54e57baf4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx @@ -41,7 +41,10 @@ import type { OnChangeProps } from '@kbn/lists-plugin/public'; import type { ValueSuggestionsGetFn } from '@kbn/unified-search-plugin/public/autocomplete/providers/value_suggestion_provider'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { useGetUpdatedTags } from '../../../../hooks/artifacts'; -import { FILTER_PROCESS_DESCENDANTS_TAG } from '../../../../../../common/endpoint/service/artifacts/constants'; +import { + FILTER_PROCESS_DESCENDANTS_TAG, + PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY_TEXT, +} from '../../../../../../common/endpoint/service/artifacts/constants'; import { isFilterProcessDescendantsEnabled, isFilterProcessDescendantsTag, @@ -547,12 +550,7 @@ export const EventFiltersForm: React.FC - - - + {PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY_TEXT} )} diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts index bb46bc2da92b..be85d8b83210 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts @@ -19,22 +19,28 @@ import { import type { TranslatedEntry, TranslatedExceptionListItem } from '../../schemas/artifacts'; import { ArtifactConstants } from './common'; import { + ENDPOINT_ARTIFACT_LISTS, ENDPOINT_BLOCKLISTS_LIST_ID, ENDPOINT_EVENT_FILTERS_LIST_ID, ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID, ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID, } from '@kbn/securitysolution-list-constants'; +import { FILTER_PROCESS_DESCENDANTS_TAG } from '../../../../common/endpoint/service/artifacts/constants'; +import type { ExperimentalFeatures } from '../../../../common'; +import { allowedExperimentalValues } from '../../../../common'; describe('artifacts lists', () => { let mockExceptionClient: ExceptionListClient; + let defaultFeatures: ExperimentalFeatures; beforeEach(() => { jest.clearAllMocks(); mockExceptionClient = listMock.getExceptionListClient(); + defaultFeatures = allowedExperimentalValues; }); - describe('getFilteredEndpointExceptionListRaw', () => { + describe('getFilteredEndpointExceptionListRaw + convertExceptionsToEndpointFormat', () => { const TEST_FILTER = 'exception-list-agnostic.attributes.os_types:"linux"'; test('it should get convert the exception lists response to the proper endpoint format', async () => { @@ -69,7 +75,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -115,7 +121,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -166,7 +172,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -219,7 +225,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -271,7 +277,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -314,7 +320,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -357,7 +363,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -386,7 +392,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); // Expect 1 exceptions, the first two calls returned the same exception list items expect(translated.entries.length).toEqual(1); @@ -402,7 +408,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated.entries.length).toEqual(0); }); @@ -516,6 +522,215 @@ describe('artifacts lists', () => { ); expect(artifact1.decodedSha256).toEqual(artifact2.decodedSha256); }); + + describe('`descendant_of` operator', () => { + let enabledProcessDescendant: ExperimentalFeatures; + + beforeEach(() => { + enabledProcessDescendant = { + ...defaultFeatures, + filterProcessDescendantsForEventFiltersEnabled: true, + }; + }); + + test('when feature flag is disabled, it should not convert `descendant_of`', async () => { + const expectedEndpointExceptions: TranslatedExceptionListItem = { + type: 'simple', + entries: [ + { + field: 'process.executable', + operator: 'included', + type: 'exact_caseless', + value: 'C:\\Windows\\System32\\ping.exe', + }, + ], + }; + + const inputEntry: EntriesArray = [ + { + field: 'process.executable.text', + operator: 'included', + type: 'match', + value: 'C:\\Windows\\System32\\ping.exe', + }, + ]; + + const exceptionMock = getFoundExceptionListItemSchemaMock(); + exceptionMock.data[0].tags.push(FILTER_PROCESS_DESCENDANTS_TAG); + exceptionMock.data[0].list_id = ENDPOINT_ARTIFACT_LISTS.eventFilters.id; + exceptionMock.data[0].entries = inputEntry; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionMock); + + const resp = await getFilteredEndpointExceptionListRaw({ + elClient: mockExceptionClient, + filter: TEST_FILTER, + listId: ENDPOINT_LIST_ID, + }); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', { + filterProcessDescendantsForEventFiltersEnabled: false, + } as ExperimentalFeatures); + + expect(translated).toEqual({ entries: [expectedEndpointExceptions] }); + }); + + test.each([ + ENDPOINT_ARTIFACT_LISTS.blocklists.id, + ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id, + ENDPOINT_ARTIFACT_LISTS.trustedApps.id, + ])('when %s, it should not convert `descendant_of`', async (listId) => { + const expectedEndpointExceptions: TranslatedExceptionListItem = { + type: 'simple', + entries: [ + { + field: 'process.executable', + operator: 'included', + type: 'exact_caseless', + value: 'C:\\Windows\\System32\\ping.exe', + }, + ], + }; + + const inputEntry: EntriesArray = [ + { + field: 'process.executable.text', + operator: 'included', + type: 'match', + value: 'C:\\Windows\\System32\\ping.exe', + }, + ]; + + const exceptionMock = getFoundExceptionListItemSchemaMock(); + exceptionMock.data[0].tags.push(FILTER_PROCESS_DESCENDANTS_TAG); + exceptionMock.data[0].list_id = listId; + exceptionMock.data[0].entries = inputEntry; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionMock); + + const resp = await getFilteredEndpointExceptionListRaw({ + elClient: mockExceptionClient, + filter: TEST_FILTER, + listId: ENDPOINT_LIST_ID, + }); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', enabledProcessDescendant); + + expect(translated).toEqual({ entries: [expectedEndpointExceptions] }); + }); + + test('it should convert `descendant_of` to the expected format', async () => { + const expectedEndpointExceptions: TranslatedExceptionListItem = { + type: 'simple', + entries: [ + { + operator: 'included', + type: 'descendent_of', + value: { + entries: [ + { + type: 'simple', + entries: [ + { + field: 'process.executable', + operator: 'included', + type: 'exact_caseless', + value: 'C:\\Windows\\System32\\ping.exe', + }, + { + field: 'event.category', + operator: 'included', + type: 'exact_cased', + value: 'process', + }, + ], + }, + ], + }, + }, + ], + }; + + const inputEntry: EntriesArray = [ + { + field: 'process.executable.text', + operator: 'included', + type: 'match', + value: 'C:\\Windows\\System32\\ping.exe', + }, + ]; + + const exceptionMock = getFoundExceptionListItemSchemaMock(); + exceptionMock.data[0].tags.push(FILTER_PROCESS_DESCENDANTS_TAG); + exceptionMock.data[0].list_id = ENDPOINT_ARTIFACT_LISTS.eventFilters.id; + exceptionMock.data[0].entries = inputEntry; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionMock); + + const resp = await getFilteredEndpointExceptionListRaw({ + elClient: mockExceptionClient, + filter: TEST_FILTER, + listId: ENDPOINT_LIST_ID, + }); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', enabledProcessDescendant); + + expect(translated).toEqual({ entries: [expectedEndpointExceptions] }); + }); + + test('it should handle nested entries properly', async () => { + const expectedEndpointExceptions: TranslatedExceptionListItem = { + type: 'simple', + entries: [ + { + operator: 'included', + type: 'descendent_of', + value: { + entries: [ + { + type: 'simple', + entries: [ + { + entries: [ + { + field: 'nested.field', + operator: 'included', + type: 'exact_cased', + value: 'some value', + }, + ], + field: 'some.parentField', + type: 'nested', + }, + { + field: 'some.not.nested.field', + operator: 'included', + type: 'exact_cased', + value: 'some value', + }, + { + field: 'event.category', + operator: 'included', + type: 'exact_cased', + value: 'process', + }, + ], + }, + ], + }, + }, + ], + }; + + const exceptionMock = getFoundExceptionListItemSchemaMock(); + exceptionMock.data[0].tags.push(FILTER_PROCESS_DESCENDANTS_TAG); + exceptionMock.data[0].list_id = ENDPOINT_ARTIFACT_LISTS.eventFilters.id; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionMock); + + const resp = await getFilteredEndpointExceptionListRaw({ + elClient: mockExceptionClient, + filter: TEST_FILTER, + listId: ENDPOINT_LIST_ID, + }); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', enabledProcessDescendant); + + expect(translated).toEqual({ entries: [expectedEndpointExceptions] }); + }); + }); }); describe('Endpoint Artifacts', () => { @@ -562,7 +777,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -607,7 +822,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -646,7 +861,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -710,7 +925,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], @@ -750,7 +965,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -815,7 +1030,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -864,7 +1079,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -911,7 +1126,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -952,7 +1167,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -1019,7 +1234,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -1059,7 +1274,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -1126,7 +1341,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -1174,7 +1389,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM); expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ @@ -1198,7 +1413,7 @@ describe('artifacts lists', () => { os: 'macos', listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM); @@ -1224,7 +1439,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_EVENT_FILTERS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM); expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ @@ -1249,7 +1464,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM); expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ @@ -1274,7 +1489,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_BLOCKLISTS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM); expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts index 3b9690c902fc..8040a4f59698 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts @@ -26,6 +26,9 @@ import { } from '@kbn/securitysolution-list-constants'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import { validate } from '@kbn/securitysolution-io-ts-utils'; +import { PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY } from '../../../../common/endpoint/service/artifacts/constants'; +import type { ExperimentalFeatures } from '../../../../common'; +import { isFilterProcessDescendantsEnabled } from '../../../../common/endpoint/service/artifacts/utils'; import type { InternalArtifactCompleteSchema, TranslatedEntry, @@ -36,6 +39,7 @@ import type { TranslatedEntryNestedEntry, TranslatedExceptionListItem, WrappedTranslatedExceptionList, + TranslatedEntriesOfDescendantOf, } from '../../schemas'; import { translatedPerformantEntries as translatedPerformantEntriesType, @@ -78,10 +82,11 @@ export type ArtifactListId = export function convertExceptionsToEndpointFormat( exceptions: ExceptionListItemSchema[], - schemaVersion: string + schemaVersion: string, + experimentalFeatures: ExperimentalFeatures ) { const translatedExceptions = { - entries: translateToEndpointExceptions(exceptions, schemaVersion), + entries: translateToEndpointExceptions(exceptions, schemaVersion, experimentalFeatures), }; const [validated, errors] = validate(translatedExceptions, wrappedTranslatedExceptionList); if (errors != null) { @@ -151,13 +156,24 @@ export async function getAllItemsFromEndpointExceptionList({ * Translates Exception list items to Exceptions the endpoint can understand * @param exceptions * @param schemaVersion + * @param experimentalFeatures */ export function translateToEndpointExceptions( exceptions: ExceptionListItemSchema[], - schemaVersion: string + schemaVersion: string, + experimentalFeatures: ExperimentalFeatures ): TranslatedExceptionListItem[] { const entrySet = new Set(); - const entriesFiltered: TranslatedExceptionListItem[] = []; + const uniqueItems: TranslatedExceptionListItem[] = []; + const storeUniqueItem = (item: TranslatedExceptionListItem) => { + const entryHash = createHash('sha256').update(JSON.stringify(item)).digest('hex'); + + if (!entrySet.has(entryHash)) { + uniqueItems.push(item); + entrySet.add(entryHash); + } + }; + if (schemaVersion === 'v1') { exceptions.forEach((entry) => { // For Blocklist, we create a single entry for each blocklist entry item @@ -172,30 +188,51 @@ export function translateToEndpointExceptions( ...entry, entries: [blocklistSingleEntry], }); - const entryHash = createHash('sha256') - .update(JSON.stringify(translatedItem)) - .digest('hex'); - if (!entrySet.has(entryHash)) { - entriesFiltered.push(translatedItem); - entrySet.add(entryHash); - } + + storeUniqueItem(translatedItem); }); + } else if ( + experimentalFeatures.filterProcessDescendantsForEventFiltersEnabled && + entry.list_id === ENDPOINT_ARTIFACT_LISTS.eventFilters.id && + isFilterProcessDescendantsEnabled(entry) + ) { + const translatedItem = translateProcessDescendantEventFilter(schemaVersion, entry); + storeUniqueItem(translatedItem); } else { const translatedItem = translateItem(schemaVersion, entry); - const entryHash = createHash('sha256').update(JSON.stringify(translatedItem)).digest('hex'); - if (!entrySet.has(entryHash)) { - entriesFiltered.push(translatedItem); - entrySet.add(entryHash); - } + storeUniqueItem(translatedItem); } }); - return entriesFiltered; + return uniqueItems; } else { throw new Error('unsupported schemaVersion'); } } +function translateProcessDescendantEventFilter( + schemaVersion: string, + entry: ExceptionListItemSchema +): TranslatedExceptionListItem { + const translatedEntries: TranslatedEntriesOfDescendantOf = translateItem(schemaVersion, { + ...entry, + entries: [...entry.entries, PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY], + }) as TranslatedEntriesOfDescendantOf; + + return { + type: entry.type, + entries: [ + { + operator: 'included', + type: 'descendent_of', + value: { + entries: [translatedEntries], + }, + }, + ], + }; +} + function getMatcherFunction({ field, matchAny, @@ -246,8 +283,9 @@ function translateItem( item: ExceptionListItemSchema ): TranslatedExceptionListItem { const itemSet = new Set(); - const getEntries = (): TranslatedExceptionListItem['entries'] => { - return item.entries.reduce((translatedEntries, entry) => { + + const entries: TranslatedExceptionListItem['entries'] = item.entries.reduce( + (translatedEntries, entry) => { const translatedEntry = translateEntry(schemaVersion, item.entries, entry, item.os_types[0]); if (translatedEntry !== undefined) { @@ -272,12 +310,13 @@ function translateItem( } return translatedEntries; - }, []); - }; + }, + [] + ); return { type: item.type, - entries: getEntries(), + entries, }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts index 3abbbe129288..262ead3493df 100644 --- a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts +++ b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts @@ -84,11 +84,36 @@ export const translatedEntryNested = t.exact( }) ); +const translatedEntriesOfDescendantOf = t.type({ + type: t.string, + entries: t.array( + t.union([ + translatedEntryNested, + translatedEntryMatch, + translatedEntryMatchWildcard, + translatedEntryMatchAny, + ]) + ), +}); +export type TranslatedEntriesOfDescendantOf = t.TypeOf; + +export const translatedEntryDescendantOf = t.exact( + t.type({ + operator, + type: t.keyof({ descendent_of: null }), + value: t.type({ + entries: t.array(translatedEntriesOfDescendantOf), + }), + }) +); +export type TranslatedEntryDescendantOf = t.TypeOf; + export const translatedEntry = t.union([ translatedEntryNested, translatedEntryMatch, translatedEntryMatchWildcard, translatedEntryMatchAny, + translatedEntryDescendantOf, ]); export type TranslatedEntry = t.TypeOf; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index a7a0048df0f4..bb2ea455675c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -44,6 +44,8 @@ import { createFetchAllArtifactsIterableMock, generateArtifactMock, } from '@kbn/fleet-plugin/server/services/artifacts/mocks'; +import type { ExperimentalFeatures } from '../../../../../common'; +import { allowedExperimentalValues } from '../../../../../common'; const getArtifactObject = (artifact: InternalArtifactSchema) => JSON.parse(Buffer.from(artifact.body!, 'base64').toString()); @@ -93,6 +95,8 @@ describe('ManifestManager', () => { let ARTIFACT_TRUSTED_APPS_MACOS: InternalArtifactCompleteSchema; let ARTIFACT_TRUSTED_APPS_WINDOWS: InternalArtifactCompleteSchema; + let defaultFeatures: ExperimentalFeatures; + beforeAll(async () => { ARTIFACTS = await getMockArtifacts(); ARTIFACTS_BY_ID = { @@ -106,6 +110,7 @@ describe('ManifestManager', () => { ARTIFACT_EXCEPTIONS_WINDOWS = ARTIFACTS[1]; ARTIFACT_TRUSTED_APPS_MACOS = ARTIFACTS[3]; ARTIFACT_TRUSTED_APPS_WINDOWS = ARTIFACTS[4]; + defaultFeatures = allowedExperimentalValues; }); describe('getLastComputedManifest from Unified Manifest SO', () => { @@ -463,29 +468,33 @@ describe('ManifestManager', () => { expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + entries: translateToEndpointExceptions([exceptionListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[8])).toStrictEqual({ - entries: translateToEndpointExceptions([eventFiltersListItem], 'v1'), + entries: translateToEndpointExceptions([eventFiltersListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[11])).toStrictEqual({ - entries: translateToEndpointExceptions([hostIsolationExceptionsItem], 'v1'), + entries: translateToEndpointExceptions( + [hostIsolationExceptionsItem], + 'v1', + defaultFeatures + ), }); expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[14])).toStrictEqual({ - entries: translateToEndpointExceptions([blocklistsListItem], 'v1'), + entries: translateToEndpointExceptions([blocklistsListItem], 'v1', defaultFeatures), }); for (const artifact of artifacts) { @@ -553,22 +562,26 @@ describe('ManifestManager', () => { expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[8])).toStrictEqual({ - entries: translateToEndpointExceptions([eventFiltersListItem], 'v1'), + entries: translateToEndpointExceptions([eventFiltersListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[11])).toStrictEqual({ - entries: translateToEndpointExceptions([hostIsolationExceptionsItem], 'v1'), + entries: translateToEndpointExceptions( + [hostIsolationExceptionsItem], + 'v1', + defaultFeatures + ), }); expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[14])).toStrictEqual({ - entries: translateToEndpointExceptions([blocklistsListItem], 'v1'), + entries: translateToEndpointExceptions([blocklistsListItem], 'v1', defaultFeatures), }); for (const artifact of artifacts) { @@ -647,16 +660,20 @@ describe('ManifestManager', () => { expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + entries: translateToEndpointExceptions([exceptionListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[1])).toStrictEqual({ - entries: translateToEndpointExceptions([duplicatedEndpointExceptionInDifferentOS], 'v1'), + entries: translateToEndpointExceptions( + [duplicatedEndpointExceptionInDifferentOS], + 'v1', + defaultFeatures + ), }); expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); @@ -664,16 +681,21 @@ describe('ManifestManager', () => { expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: translateToEndpointExceptions( [eventFiltersListItem, duplicatedEventFilterInDifferentPolicy], - 'v1' + 'v1', + defaultFeatures ), }); expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[11])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[12])).toStrictEqual({ - entries: translateToEndpointExceptions([hostIsolationExceptionsItem], 'v1'), + entries: translateToEndpointExceptions( + [hostIsolationExceptionsItem], + 'v1', + defaultFeatures + ), }); expect(getArtifactObject(artifacts[13])).toStrictEqual({ - entries: translateToEndpointExceptions([blocklistsListItem], 'v1'), + entries: translateToEndpointExceptions([blocklistsListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[14])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[15])).toStrictEqual({ entries: [] }); @@ -750,19 +772,20 @@ describe('ManifestManager', () => { expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + entries: translateToEndpointExceptions([exceptionListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: translateToEndpointExceptions( [trustedAppListItem, trustedAppListItemPolicy2], - 'v1' + 'v1', + defaultFeatures ), }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); @@ -855,19 +878,19 @@ describe('ManifestManager', () => { expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + entries: translateToEndpointExceptions([exceptionListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[8])).toStrictEqual({ - entries: translateToEndpointExceptions([eventFiltersListItem], 'v1'), + entries: translateToEndpointExceptions([eventFiltersListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] }); @@ -875,7 +898,7 @@ describe('ManifestManager', () => { expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[14])).toStrictEqual({ - entries: translateToEndpointExceptions([blocklistsListItem], 'v1'), + entries: translateToEndpointExceptions([blocklistsListItem], 'v1', defaultFeatures), }); for (const artifact of artifacts) { @@ -936,29 +959,33 @@ describe('ManifestManager', () => { expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + entries: translateToEndpointExceptions([exceptionListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[8])).toStrictEqual({ - entries: translateToEndpointExceptions([eventFiltersListItem], 'v1'), + entries: translateToEndpointExceptions([eventFiltersListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[11])).toStrictEqual({ - entries: translateToEndpointExceptions([hostIsolationExceptionsItem], 'v1'), + entries: translateToEndpointExceptions( + [hostIsolationExceptionsItem], + 'v1', + defaultFeatures + ), }); expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[14])).toStrictEqual({ - entries: translateToEndpointExceptions([blocklistsListItem], 'v1'), + entries: translateToEndpointExceptions([blocklistsListItem], 'v1', defaultFeatures), }); for (const artifact of artifacts) { @@ -1079,7 +1106,7 @@ describe('ManifestManager', () => { expect(artifacts.length).toBe(15); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([expectedExceptionListItem], 'v1'), + entries: translateToEndpointExceptions([expectedExceptionListItem], 'v1', defaultFeatures), }); }); @@ -1119,7 +1146,7 @@ describe('ManifestManager', () => { expect(artifacts.length).toBe(15); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([expectedExceptionListItem], 'v1'), + entries: translateToEndpointExceptions([expectedExceptionListItem], 'v1', defaultFeatures), }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 3a6cfc5be280..f10dbb1ab3a5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -189,7 +189,7 @@ export class ManifestManager { const exceptions: ExceptionListItemSchema[] = listId === ENDPOINT_LIST_ID ? allExceptionsByListId : allExceptionsByListId.filter(filter); - return convertExceptionsToEndpointFormat(exceptions, schemaVersion); + return convertExceptionsToEndpointFormat(exceptions, schemaVersion, this.experimentalFeatures); } /**