Skip to content

Commit 58d97f5

Browse files
[Security Solution] Validate exception list size when adding new items (#73399) (#73600)
* Validate exception list size when adding new items * Update comment * Extract list size validation and apply to endpoint route also Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 5e2bde2 commit 58d97f5

8 files changed

Lines changed: 123 additions & 3 deletions

File tree

x-pack/plugins/lists/common/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,5 @@ export const ENDPOINT_LIST_NAME = 'Elastic Endpoint Security Exception List';
4848

4949
/** The description of the single global space agnostic endpoint list */
5050
export const ENDPOINT_LIST_DESCRIPTION = 'Elastic Endpoint Security Exception List';
51+
52+
export const MAX_EXCEPTION_LIST_SIZE = 10000;

x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { IRouter } from 'kibana/server';
88

9-
import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants';
9+
import { ENDPOINT_LIST_ID, ENDPOINT_LIST_ITEM_URL } from '../../common/constants';
1010
import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps';
1111
import { validate } from '../../common/siem_common_deps';
1212
import {
@@ -16,6 +16,7 @@ import {
1616
} from '../../common/schemas';
1717

1818
import { getExceptionListClient } from './utils/get_exception_list_client';
19+
import { validateExceptionListSize } from './validate';
1920

2021
export const createEndpointListItemRoute = (router: IRouter): void => {
2122
router.post(
@@ -71,6 +72,18 @@ export const createEndpointListItemRoute = (router: IRouter): void => {
7172
if (errors != null) {
7273
return siemResponse.error({ body: errors, statusCode: 500 });
7374
} else {
75+
const listSizeError = await validateExceptionListSize(
76+
exceptionLists,
77+
ENDPOINT_LIST_ID,
78+
'agnostic'
79+
);
80+
if (listSizeError != null) {
81+
await exceptionLists.deleteExceptionListItemById({
82+
id: createdList.id,
83+
namespaceType: 'agnostic',
84+
});
85+
return siemResponse.error(listSizeError);
86+
}
7487
return response.ok({ body: validated ?? {} });
7588
}
7689
}

x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717

1818
import { getExceptionListClient } from './utils/get_exception_list_client';
1919
import { endpointDisallowedFields } from './endpoint_disallowed_fields';
20+
import { validateExceptionListSize } from './validate';
2021

2122
export const createExceptionListItemRoute = (router: IRouter): void => {
2223
router.post(
@@ -104,6 +105,18 @@ export const createExceptionListItemRoute = (router: IRouter): void => {
104105
if (errors != null) {
105106
return siemResponse.error({ body: errors, statusCode: 500 });
106107
} else {
108+
const listSizeError = await validateExceptionListSize(
109+
exceptionLists,
110+
listId,
111+
namespaceType
112+
);
113+
if (listSizeError != null) {
114+
await exceptionLists.deleteExceptionListItemById({
115+
id: createdList.id,
116+
namespaceType,
117+
});
118+
return siemResponse.error(listSizeError);
119+
}
107120
return response.ok({ body: validated ?? {} });
108121
}
109122
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { ExceptionListClient } from '../services/exception_lists/exception_list_client';
8+
import { MAX_EXCEPTION_LIST_SIZE } from '../../common/constants';
9+
import { foundExceptionListItemSchema } from '../../common/schemas';
10+
import { NamespaceType } from '../../common/schemas/types';
11+
import { validate } from '../../common/siem_common_deps';
12+
13+
export const validateExceptionListSize = async (
14+
exceptionLists: ExceptionListClient,
15+
listId: string,
16+
namespaceType: NamespaceType
17+
): Promise<{ body: string; statusCode: number } | null> => {
18+
const exceptionListItems = await exceptionLists.findExceptionListItem({
19+
filter: undefined,
20+
listId,
21+
namespaceType,
22+
page: undefined,
23+
perPage: undefined,
24+
sortField: undefined,
25+
sortOrder: undefined,
26+
});
27+
if (exceptionListItems == null) {
28+
// If exceptionListItems is null then we couldn't find the list so it may have been deleted
29+
return {
30+
body: `Unable to find list id: ${listId} to verify max exception list size`,
31+
statusCode: 500,
32+
};
33+
}
34+
const [validatedItems, err] = validate(exceptionListItems, foundExceptionListItemSchema);
35+
if (err != null) {
36+
return {
37+
body: err,
38+
statusCode: 500,
39+
};
40+
}
41+
// Unnecessary since validatedItems comes from exceptionListItems which is already
42+
// checked for null, but typescript fails to detect that
43+
if (validatedItems == null) {
44+
return {
45+
body: `Unable to find list id: ${listId} to verify max exception list size`,
46+
statusCode: 500,
47+
};
48+
}
49+
if (validatedItems.total > MAX_EXCEPTION_LIST_SIZE) {
50+
return {
51+
body: `Failed to add exception item, exception list would exceed max size of ${MAX_EXCEPTION_LIST_SIZE}`,
52+
statusCode: 400,
53+
};
54+
}
55+
return null;
56+
};

x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { SavedObjectsClientContract } from 'kibana/server';
88

99
import {
1010
ExceptionListItemSchema,
11+
Id,
1112
IdOrUndefined,
1213
ItemIdOrUndefined,
1314
NamespaceType,
@@ -23,6 +24,12 @@ interface DeleteExceptionListItemOptions {
2324
savedObjectsClient: SavedObjectsClientContract;
2425
}
2526

27+
interface DeleteExceptionListItemByIdOptions {
28+
id: Id;
29+
namespaceType: NamespaceType;
30+
savedObjectsClient: SavedObjectsClientContract;
31+
}
32+
2633
export const deleteExceptionListItem = async ({
2734
itemId,
2835
id,
@@ -43,3 +50,12 @@ export const deleteExceptionListItem = async ({
4350
return exceptionListItem;
4451
}
4552
};
53+
54+
export const deleteExceptionListItemById = async ({
55+
id,
56+
namespaceType,
57+
savedObjectsClient,
58+
}: DeleteExceptionListItemByIdOptions): Promise<void> => {
59+
const savedObjectType = getSavedObjectType({ namespaceType });
60+
await savedObjectsClient.delete(savedObjectType, id);
61+
};

x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
CreateExceptionListItemOptions,
2121
CreateExceptionListOptions,
2222
DeleteEndpointListItemOptions,
23+
DeleteExceptionListItemByIdOptions,
2324
DeleteExceptionListItemOptions,
2425
DeleteExceptionListOptions,
2526
FindEndpointListItemOptions,
@@ -40,7 +41,7 @@ import { createExceptionListItem } from './create_exception_list_item';
4041
import { updateExceptionList } from './update_exception_list';
4142
import { updateExceptionListItem } from './update_exception_list_item';
4243
import { deleteExceptionList } from './delete_exception_list';
43-
import { deleteExceptionListItem } from './delete_exception_list_item';
44+
import { deleteExceptionListItem, deleteExceptionListItemById } from './delete_exception_list_item';
4445
import { findExceptionListItem } from './find_exception_list_item';
4546
import { findExceptionList } from './find_exception_list';
4647
import { findExceptionListsItem } from './find_exception_list_items';
@@ -326,6 +327,18 @@ export class ExceptionListClient {
326327
});
327328
};
328329

330+
public deleteExceptionListItemById = async ({
331+
id,
332+
namespaceType,
333+
}: DeleteExceptionListItemByIdOptions): Promise<void> => {
334+
const { savedObjectsClient } = this;
335+
return deleteExceptionListItemById({
336+
id,
337+
namespaceType,
338+
savedObjectsClient,
339+
});
340+
};
341+
329342
/**
330343
* This is the same as "deleteExceptionListItem" except it applies specifically to the endpoint list.
331344
*/

x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
ExceptionListType,
2020
ExceptionListTypeOrUndefined,
2121
FilterOrUndefined,
22+
Id,
2223
IdOrUndefined,
2324
Immutable,
2425
ItemId,
@@ -93,6 +94,11 @@ export interface DeleteExceptionListItemOptions {
9394
namespaceType: NamespaceType;
9495
}
9596

97+
export interface DeleteExceptionListItemByIdOptions {
98+
id: Id;
99+
namespaceType: NamespaceType;
100+
}
101+
96102
export interface DeleteEndpointListItemOptions {
97103
id: IdOrUndefined;
98104
itemId: ItemIdOrUndefined;

x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { ListArrayOrUndefined } from '../../../../common/detection_engine/schema
1515
import { BulkResponse, BulkResponseErrorAggregation, isValidUnit } from './types';
1616
import { BuildRuleMessage } from './rule_messages';
1717
import { hasLargeValueList } from '../../../../common/detection_engine/utils';
18+
import { MAX_EXCEPTION_LIST_SIZE } from '../../../../../lists/common/constants';
1819

1920
interface SortExceptionsReturn {
2021
exceptionsWithValueLists: ExceptionListItemSchema[];
@@ -183,7 +184,7 @@ export const getExceptions = async ({
183184
listId: foundList.list_id,
184185
namespaceType,
185186
page: 1,
186-
perPage: 10000,
187+
perPage: MAX_EXCEPTION_LIST_SIZE,
187188
filter: undefined,
188189
sortOrder: undefined,
189190
sortField: undefined,

0 commit comments

Comments
 (0)