Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
907fe03
[api/validator] allow importing endpoint artifacts
gergoabraham Jan 7, 2026
fc30eee
[api/test] extract generateItem into generator
gergoabraham Jan 7, 2026
f4a010b
[api/test] add initial api test for import
gergoabraham Jan 7, 2026
8af0e08
[api/test] move existing import tests to new suite
gergoabraham Jan 7, 2026
82f7ea9
[api/validator] unify error message across artifacts
gergoabraham Dec 5, 2025
dd1252f
[api/validator] tie import to ALL privilege
gergoabraham Dec 5, 2025
840dfd0
[chore] make tests run on serverless with custom roles
gergoabraham Dec 5, 2025
fcb7ab0
[api/validator] add policy:all only with missing spaceOwnerId
gergoabraham Dec 8, 2025
59ce503
[api/lists] allow passing extra error info to import API
gergoabraham Dec 8, 2025
68d7177
[api/validator] tie to global artifact privilege importing to other s…
gergoabraham Dec 8, 2025
9fd319b
[api/validator] tie to global artifact privilege importing global art…
gergoabraham Dec 8, 2025
6ee3b86
[api/validator] remove invalid policy ids
gergoabraham Dec 9, 2025
cd0f023
[chore] rename test entry file to a more logical name
gergoabraham Jan 8, 2026
f027f3a
[chore] adapt to changes in #248962
gergoabraham Mar 2, 2026
21e2170
[chore] fix some commit issues
gergoabraham Mar 2, 2026
a61eede
[chore] unskip tests
gergoabraham Mar 2, 2026
ad15fc6
adapt test to exceptions privileges split from security privileges
gergoabraham Mar 3, 2026
bc3f733
[lists] add bulkDeleteExceptionListItems function
gergoabraham Mar 6, 2026
3b48928
[lists] refactor: remove old and slow deleteFoundExceptionListItems
gergoabraham Mar 6, 2026
367cb86
[api/import] remove visible artifacts on import for non-global access…
gergoabraham Mar 7, 2026
96445bd
[api/import] remove visible artifacts on import for global access users
gergoabraham Mar 7, 2026
52f1312
Changes from node scripts/lint_ts_projects --fix
kibanamachine Mar 7, 2026
13d8244
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Mar 7, 2026
5961bdf
[api/import] lets not delete existing artifacts with overwrite=false :)
gergoabraham Mar 7, 2026
6f61ea3
[api/test] add test case for `new_list` query param
gergoabraham Mar 9, 2026
dc2537a
[test/chore] switch `expect` implementation so `expect.any` can be used
gergoabraham Mar 9, 2026
d17f996
[api/import] add comment to imported artifacts
gergoabraham Mar 9, 2026
a23499a
[api/import] add new imported_artifact tag to imported artifacts
gergoabraham Mar 9, 2026
a3690bd
[api/test] add test cases for existing compatibility functionalities …
gergoabraham Mar 9, 2026
c5b277f
[api/import] add spaceOwnerId to artifacts not having one
gergoabraham Mar 9, 2026
e5f8c38
[api/validator] do not allow importing artifacts not visible in curre…
gergoabraham Mar 9, 2026
92fd180
[api/validator] do not import artifacts with invalid space IDs
gergoabraham Mar 10, 2026
5f8668b
[api/validator] validate only "agnostic" namespace items
gergoabraham Mar 10, 2026
b01b020
[api/import] fix compatibility: add global tag only if namespace is "…
gergoabraham Mar 10, 2026
0fe5817
[api/validator] some tweaks, refactor
gergoabraham Mar 10, 2026
5c09cfe
[api/validators] add usual item validators to import hook
gergoabraham Mar 10, 2026
00115cb
[ui] call _import with overwrite=true
gergoabraham Mar 10, 2026
a8743d6
[ui] improve message
gergoabraham Mar 10, 2026
58fcfb3
[temp/ui] add extra messages to toast to help testing
gergoabraham Mar 10, 2026
6458bbc
[optimization] get space id string without reading space SO
gergoabraham Mar 10, 2026
dbddf35
Merge branch 'main' into artifact-transfer-3-import-api
gergoabraham Mar 12, 2026
851bb4a
fix cy for serverless
gergoabraham Mar 12, 2026
8fc93cc
[api/test] fix unit tests
gergoabraham Mar 12, 2026
36e8ea2
[api/test] add unit test coverage to new base validator functions
gergoabraham Mar 12, 2026
6043d03
[ui] refine success/error toast test a bit
gergoabraham Mar 13, 2026
ba330c4
Merge branch 'main' into artifact-transfer-3-import-api
gergoabraham Mar 13, 2026
89fbdeb
fix unit test
gergoabraham Mar 13, 2026
8ee1a8c
Merge branch 'main' into artifact-transfer-3-import-api
gergoabraham Mar 16, 2026
f16665b
[api/validator] add return type to functions
gergoabraham Mar 17, 2026
5a64b4f
[api/validator] switch deletion log to debug level
gergoabraham Mar 17, 2026
ed219a4
[api/validator] handle any number of artifact deletion
gergoabraham Mar 17, 2026
e1ba1ff
[api/validator] get rid of the multiple iterations
gergoabraham Mar 17, 2026
9b101bf
[api/test] change test config constant to readonly
gergoabraham Mar 17, 2026
bdaca74
Merge branch 'main' into artifact-transfer-3-import-api
gergoabraham Mar 17, 2026
067fc20
[api/validator] log.debug again
gergoabraham Mar 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const bulkErrorErrorSchema = t.exact(
})
);

export type BulkErrorErrorSchema = t.TypeOf<typeof bulkErrorErrorSchema>;

export const bulkErrorSchema = t.intersection([
t.exact(
t.type({
Expand Down
1 change: 0 additions & 1 deletion x-pack/solutions/security/plugins/lists/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ dependsOn:
- '@kbn/core-http-server'
- '@kbn/securitysolution-es-utils'
- '@kbn/securitysolution-io-ts-types'
- '@kbn/std'
- '@kbn/utils'
- '@kbn/logging-mocks'
- '@kbn/utility-types'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { BulkErrorErrorSchema } from '@kbn/securitysolution-io-ts-list-types';

import { ListsErrorWithStatusCode } from '.';

export class ExceptionItemImportError extends Error implements BulkErrorErrorSchema {
public readonly status_code: number;

constructor(error: Error, public readonly listId: string, public readonly itemId: string) {
super(error.message);
this.status_code = error instanceof ListsErrorWithStatusCode ? error.getStatusCode() : 400;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { NamespaceType } from '@kbn/securitysolution-io-ts-list-types';
import { getSavedObjectType } from '@kbn/securitysolution-list-utils';
import type { SavedObjectsBulkDeleteObject, SavedObjectsClientContract } from '@kbn/core/server';

interface BulkDeleteExceptionListItemsOptions {
ids: string[];
namespaceType: NamespaceType;
savedObjectsClient: SavedObjectsClientContract;
}

export const bulkDeleteExceptionListItems = async ({
ids,
namespaceType,
savedObjectsClient,
}: BulkDeleteExceptionListItemsOptions): Promise<void> => {
const savedObjectType = getSavedObjectType({ namespaceType });

const bulkDeleteObjects = ids.map<SavedObjectsBulkDeleteObject>((id) => ({
id,
type: savedObjectType,
}));

await savedObjectsClient.bulkDelete(bulkDeleteObjects);
Comment thread
paul-tavares marked this conversation as resolved.
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ import type {
ListId,
NamespaceType,
} from '@kbn/securitysolution-io-ts-list-types';
import { getSavedObjectType } from '@kbn/securitysolution-list-utils';
import { asyncForEach } from '@kbn/std';
import type { SavedObjectsClientContract } from '@kbn/core/server';

import { findExceptionListItemPointInTimeFinder } from './find_exception_list_item_point_in_time_finder';
import { bulkDeleteExceptionListItems } from './bulk_delete_exception_list_items';

interface DeleteExceptionListItemByListOptions {
listId: ListId;
Expand All @@ -28,7 +27,7 @@ export const deleteExceptionListItemByList = async ({
namespaceType,
}: DeleteExceptionListItemByListOptions): Promise<void> => {
const ids = await getExceptionListItemIds({ listId, namespaceType, savedObjectsClient });
await deleteFoundExceptionListItems({ ids, namespaceType, savedObjectsClient });
await bulkDeleteExceptionListItems({ ids, namespaceType, savedObjectsClient });
};

export const getExceptionListItemIds = async ({
Expand Down Expand Up @@ -56,30 +55,3 @@ export const getExceptionListItemIds = async ({
});
return ids;
};

/**
* NOTE: This is slow and terrible as we are deleting everything one at a time.
* TODO: Replace this with a bulk call or a delete by query would be more useful
*/
export const deleteFoundExceptionListItems = async ({
ids,
savedObjectsClient,
namespaceType,
}: {
ids: string[];
savedObjectsClient: SavedObjectsClientContract;
namespaceType: NamespaceType;
}): Promise<void> => {
const savedObjectType = getSavedObjectType({ namespaceType });
await asyncForEach(ids, async (id) => {
try {
await savedObjectsClient.delete(savedObjectType, id);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
// This can happen from race conditions or networking issues so deleting the id's
// like this is considered "best effort" and it is possible to get dangling pieces
// of data sitting around in which case the user has to manually clean up the data
// I am very hopeful this does not happen often or at all.
}
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,18 @@ describe('exception_list_client', () => {
return extensionPointStorageContext.exceptionPreDelete.callback;
},
],
[
'bulkDeleteExceptionListItems',
(): ReturnType<ExceptionListClient['bulkDeleteExceptionListItems']> => {
return exceptionListClient.bulkDeleteExceptionListItems({
ids: ['1', '2', '3'],
namespaceType: 'agnostic',
});
},
(): ExtensionPointStorageContextMock['exceptionPreDelete']['callback'] => {
return extensionPointStorageContext.exceptionPreDelete.callback;
},
],
[
'getEndpointListItem',
(): ReturnType<ExceptionListClient['getEndpointListItem']> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
} from '../extension_points';

import type {
BulkDeleteExceptionListItemsOptions,
ClosePointInTimeOptions,
ConstructorOptions,
CreateEndpointListItemOptions,
Expand Down Expand Up @@ -100,6 +101,7 @@ import { findValueListExceptionListItemsPointInTimeFinder } from './find_value_l
import { findExceptionListItemPointInTimeFinder } from './find_exception_list_item_point_in_time_finder';
import { duplicateExceptionListAndItems } from './duplicate_exception_list';
import { updateOverwriteExceptionListItem } from './update_overwrite_exception_list_item';
import { bulkDeleteExceptionListItems } from './bulk_delete_exception_list_items';

/**
* Class for use for exceptions that are with trusted applications or
Expand Down Expand Up @@ -831,6 +833,33 @@ export class ExceptionListClient {
});
};

/**
* Bulk delete exception list items by an `id` array
* @param options
* @param options.ids the array of ids of exception list items to delete
* @param options.namespaceType saved object namespace (single | agnostic)
*/
public bulkDeleteExceptionListItems = async ({
ids,
namespaceType,
}: BulkDeleteExceptionListItemsOptions): Promise<void> => {
const { savedObjectsClient } = this;

if (this.enableServerExtensionPoints) {
// todo: this is not ideal, items will be checked one-by-one. we'd need an `exceptionsListPreBulkDeleteItems`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this isn't worse than the code it's replacing: is there a blocker to adding a bulkGet to allow this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that's good question. I don't know about any blocker - I think this was the point where I intentionally lost momentum, and instead of going down the rabbit hole focused on getting this functionality ready

// callback, but then that also needs a bulkGet function in exceptionListClient, which we don't have yet.
for (const id of ids) {
await this.serverExtensionsClient.pipeRun(
'exceptionsListPreDeleteItem',
{ id, itemId: undefined, namespaceType },
this.getServerExtensionCallbackContext()
);
}
}

return bulkDeleteExceptionListItems({ ids, namespaceType, savedObjectsClient });
};

/**
* This is the same as "deleteExceptionListItem" except it applies specifically to the endpoint list.
* Either id or itemId has to be defined to delete but not both is required. If both are provided, the id
Expand Down Expand Up @@ -1168,18 +1197,21 @@ export class ExceptionListClient {
...readStream,
]);

let shouldListApiPerformOverwrite = overwrite;
if (this.enableServerExtensionPoints) {
await this.serverExtensionsClient.pipeRun(
const result = await this.serverExtensionsClient.pipeRun(
'exceptionsListPreImport',
parsedObjects,
{ data: parsedObjects, overwrite },
this.getServerExtensionCallbackContext()
);

shouldListApiPerformOverwrite = result.overwrite;
}

return importExceptions({
exceptions: parsedObjects,
generateNewListId,
overwrite,
overwrite: shouldListApiPerformOverwrite,
savedObjectsClient,
user,
});
Expand All @@ -1203,18 +1235,21 @@ export class ExceptionListClient {
// validation of import and sorting of lists and items
const parsedObjects = exceptionsChecksFromArray(exceptionsToImport, maxExceptionsImportSize);

let shouldListApiPerformOverwrite = overwrite;
if (this.enableServerExtensionPoints) {
await this.serverExtensionsClient.pipeRun(
const result = await this.serverExtensionsClient.pipeRun(
'exceptionsListPreImport',
parsedObjects,
{ data: parsedObjects, overwrite },
this.getServerExtensionCallbackContext()
);

shouldListApiPerformOverwrite = result.overwrite;
}

return importExceptions({
exceptions: parsedObjects,
generateNewListId: false,
overwrite,
overwrite: shouldListApiPerformOverwrite,
savedObjectsClient,
user,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,18 @@ export interface DeleteExceptionListItemByIdOptions {
namespaceType: NamespaceType;
}

/**
* ExceptionListClient.bulkDeleteExceptionListItems
* {@link ExceptionListClient.bulkDeleteExceptionListItems}
*/
export interface BulkDeleteExceptionListItemsOptions {
/** the "ids" of the exception list items */
ids: Id[];

/** saved object namespace (single | agnostic) */
namespaceType: NamespaceType;
}

/**
* ExceptionListClient.deleteEndpointListItem
* {@link ExceptionListClient.deleteEndpointListItem}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
*/

import { v4 as uuidv4 } from 'uuid';
import type {
BulkErrorSchema,
ImportExceptionListItemSchemaDecoded,
import {
type BulkErrorSchema,
type ImportExceptionListItemSchemaDecoded,
} from '@kbn/securitysolution-io-ts-list-types';

import { ExceptionItemImportError } from '../../../../exception_item_import_error';

/**
* Reports on duplicates and returns unique array of items
* @param items - exception items to be checked for duplicate list_ids
Expand All @@ -21,7 +23,14 @@ export const getTupleErrorsAndUniqueExceptionListItems = (
): [BulkErrorSchema[], ImportExceptionListItemSchemaDecoded[]] => {
const { errors, itemsAcc } = items.reduce(
(acc, parsedExceptionItem) => {
if (parsedExceptionItem instanceof Error) {
if (parsedExceptionItem instanceof ExceptionItemImportError) {
acc.errors.set(uuidv4(), {
error: parsedExceptionItem,

item_id: parsedExceptionItem.itemId,
list_id: parsedExceptionItem.listId,
});
} else if (parsedExceptionItem instanceof Error) {
acc.errors.set(uuidv4(), {
error: {
message: `Error found importing exception list item: ${parsedExceptionItem.message}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ interface ServerExtensionPointDefinition<
*/
export type ExceptionsListPreImportServerExtension = ServerExtensionPointDefinition<
'exceptionsListPreImport',
PromiseFromStreams
{ data: PromiseFromStreams; overwrite: boolean }
>;

/**
Expand Down
1 change: 0 additions & 1 deletion x-pack/solutions/security/plugins/lists/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"@kbn/core-http-server",
"@kbn/securitysolution-es-utils",
"@kbn/securitysolution-io-ts-types",
"@kbn/std",
"@kbn/utils",
"@kbn/logging-mocks",
"@kbn/utility-types",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ListOperatorTypeEnum,
type ListOperatorType,
} from '@kbn/securitysolution-io-ts-list-types';
import type { ENDPOINT_ARTIFACT_LIST_IDS } from '@kbn/securitysolution-list-constants';
import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants';
import { ConditionEntryField } from '@kbn/securitysolution-utils';
import { LIST_ITEM_ENTRY_OPERATOR_TYPES } from './common/artifact_list_item_entry_values';
Expand Down Expand Up @@ -453,4 +454,32 @@ export class ExceptionsListItemGenerator extends BaseDataGenerator<ExceptionList
...overrides,
};
}

generateEndpointArtifact = (
listId: (typeof ENDPOINT_ARTIFACT_LIST_IDS)[number],
overrides: Partial<ExceptionListItemSchema> = {}
) => {
switch (listId) {
case ENDPOINT_ARTIFACT_LISTS.endpointExceptions.id:
return this.generateEndpointException(overrides);

case ENDPOINT_ARTIFACT_LISTS.blocklists.id:
return this.generateBlocklist(overrides);

case ENDPOINT_ARTIFACT_LISTS.eventFilters.id:
return this.generateEventFilter(overrides);

case ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id:
return this.generateHostIsolationException(overrides);

case ENDPOINT_ARTIFACT_LISTS.trustedApps.id:
return this.generateTrustedApp(overrides);

case ENDPOINT_ARTIFACT_LISTS.trustedDevices.id:
return this.generateTrustedDevice(overrides);

default:
throw new Error(`Unknown listId: ${listId}. Unable to generate exception list item.`);
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const BY_POLICY_ARTIFACT_TAG_PREFIX = 'policy:';

export const GLOBAL_ARTIFACT_TAG = `${BY_POLICY_ARTIFACT_TAG_PREFIX}all`;

export const IMPORTED_ARTIFACT_TAG = 'imported_artifact';

export const ADVANCED_MODE_TAG = 'form_mode:advanced';

/** The tag name for process descendants in event filters */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export {
export {
BY_POLICY_ARTIFACT_TAG_PREFIX,
GLOBAL_ARTIFACT_TAG,
IMPORTED_ARTIFACT_TAG,
ADVANCED_MODE_TAG,
FILTER_PROCESS_DESCENDANTS_TAG,
TRUSTED_PROCESS_DESCENDANTS_TAG,
Expand Down
Loading
Loading