From a692316a4f9c00e68f0ccfffbcc9105a4e94ac8d Mon Sep 17 00:00:00 2001 From: "konrad.szwarc" Date: Tue, 31 Dec 2024 15:40:59 +0100 Subject: [PATCH 1/3] wip --- .../impl/capabilities/index.ts | 2 +- .../defend_insights/common_attributes.gen.ts | 4 ++ .../common_attributes.schema.yaml | 3 + .../insights/workflow_insights_results.tsx | 3 +- .../get_events/get_file_events_query.ts | 2 +- .../assistant/tools/defend_insights/index.ts | 16 +++-- .../output_parsers/incompatible_antivirus.ts | 4 ++ .../prompts/incompatible_antivirus.ts | 2 +- .../builders/incompatible_antivirus.ts | 69 ++++++++++++++----- 9 files changed, 81 insertions(+), 24 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/capabilities/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/capabilities/index.ts index 35f4b88ef7174..623810f956ada 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/capabilities/index.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/capabilities/index.ts @@ -21,5 +21,5 @@ export type AssistantFeatureKey = keyof AssistantFeatures; export const defaultAssistantFeatures = Object.freeze({ assistantModelEvaluation: false, attackDiscoveryAlertFiltering: false, - defendInsights: false, + defendInsights: true, }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.gen.ts index e070c3129e192..55223cb7c7617 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.gen.ts @@ -36,6 +36,10 @@ export const DefendInsightEvent = z.object({ * The value of the event */ value: z.string(), + /** + * The signature of the event + */ + signature: z.string().optional(), }); /** diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.schema.yaml index 5c27449c7d346..d928d855cdc19 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.schema.yaml @@ -23,6 +23,9 @@ components: value: description: The value of the event type: string + signature: + description: The signature of the event + type: string DefendInsightType: description: The insight type (ie. incompatible_antivirus) diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_results.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_results.tsx index 719c56b0ad033..9b28e2a2e9add 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_results.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_results.tsx @@ -122,7 +122,8 @@ export const WorkflowInsightsResults = ({ {insight.message} - {item.entries[0].type === 'match' && item.entries[0].value} + {item.entries[0].field === 'process.executable.caseless' && + item.entries[0].value} diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/get_events/get_file_events_query.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/get_events/get_file_events_query.ts index fa8f6fa1e33b4..24d19e12357c7 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/get_events/get_file_events_query.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/get_events/get_file_events_query.ts @@ -14,7 +14,7 @@ const SIZE = 200; export function getFileEventsQuery({ endpointIds }: { endpointIds: string[] }): SearchRequest { return { allow_no_indices: true, - fields: ['_id', 'agent.id', 'process.executable'], + fields: ['_id', 'agent.id', 'process.executable', 'process.code_signature.signing_id'], query: { bool: { must: [ diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/index.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/index.ts index d09e8d8f8c8a7..8ed3fac8ba32d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/index.ts @@ -105,13 +105,21 @@ export const DEFEND_INSIGHTS_TOOL: AssistantTool = Object.freeze({ outputParser: outputFixingParser, }); + // console.log(JSON.stringify(prompt, null, 2)); + + const query = getDefendInsightsPrompt({ + type: insightType, + events: anonymizedEvents, + }); + // console.log(JSON.stringify(query, null, 2)); + const result = await answerFormattingChain.call({ - query: getDefendInsightsPrompt({ - type: insightType, - events: anonymizedEvents, - }), + query, timeout: langChainTimeout, }); + + // console.log(JSON.stringify(result, null, 2)); + const insights: DefendInsight[] = result.records; await securityWorkflowInsightsService.createFromDefendInsights(insights, request); diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts index b6430e4408355..3c37667637269 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts @@ -18,6 +18,10 @@ export function getIncompatibleVirusOutputParser() { .object({ id: z.string().describe('The event ID'), endpointId: z.string().describe('The endpoint ID'), + signature: z + .string() + .optional() + .describe('The process.code_signature.signing_id value of the event, if present'), value: z.string().describe('The process.executable value of the event'), }) .array() diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts index 516de86a30975..0ead14f0b51b3 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts @@ -6,7 +6,7 @@ */ export function getIncompatibleAntivirusPrompt({ events }: { events: string[] }): string { - return `You are an Elastic Security user tasked with analyzing file events from Elastic Security to identify antivirus processes. Only focus on detecting antivirus processes. Ignore processes that belong to Elastic Agent or Elastic Defend, that are not antivirus processes, or are typical processes built into the operating system. Accuracy is of the utmost importance, try to minimize false positives. Group the processes by the antivirus program, keeping track of the agent.id and _id associated to each of the individual events as endpointId and eventId respectively. If there are no events, ignore the group field. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. + return `You are an Elastic Security user tasked with analyzing file events from Elastic Security to identify antivirus processes. Only focus on detecting antivirus processes. Ignore processes that belong to Elastic Agent or Elastic Defend, that are not antivirus processes, or are typical processes built into the operating system. Accuracy is of the utmost importance, try to minimize false positives. Group the processes by the antivirus program, keeping track of the agent.id and _id associated to each of the individual events as endpointId and eventId respectively. If there are no events, ignore the group field. If the process.code_signature.signing_id field is present in an event, set its value in the signature field. If the process.code_signature.signing_id field is absent, ignore the signature field. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. Use context from the following process events to provide insights: """ diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.ts index 64485995c578d..0c38079d891fc 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.ts @@ -33,9 +33,12 @@ export async function buildIncompatibleAntivirusWorkflowInsights( const osEndpointIdsMap = await groupEndpointIdsByOS(endpointIds, endpointMetadataService); return defendInsights.flatMap((defendInsight: DefendInsight) => { const filePaths = Array.from(new Set((defendInsight.events ?? []).map((event) => event.value))); + const signatures = Array.from( + new Set((defendInsight.events ?? []).map((event) => event.signature)) + ).filter(Boolean) as string[]; - return Object.keys(osEndpointIdsMap).flatMap((os) => - filePaths.map((filePath) => ({ + return Object.keys(osEndpointIdsMap).flatMap((os) => { + const common = { '@timestamp': currentTime, // TODO add i18n support message: 'Incompatible antiviruses detected', @@ -57,32 +60,66 @@ export async function buildIncompatibleAntivirusWorkflowInsights( timestamp: currentTime, }, value: defendInsight.group, + metadata: { + notes: { + llm_model: apiConfig.model ?? '', + }, + }, remediation: { exception_list_items: [ { list_id: ENDPOINT_ARTIFACT_LISTS.trustedApps.id, name: defendInsight.group, description: 'Suggested by Security Workflow Insights', - entries: [ - { - field: 'process.executable.caseless', - operator: 'included', - type: 'match', - value: filePath, - }, - ], + entries: [], // TODO add per policy support tags: ['policy:all'], os_types: [os as SupportedHostOsType], }, ], }, - metadata: { - notes: { - llm_model: apiConfig.model ?? '', + }; + if (signatures.length) { + return signatures.map((signature) => ({ + ...common, + remediation: { + ...common.remediation, + exception_list_items: [ + { + ...common.remediation.exception_list_items[0], + entries: [ + { + field: os === 'macos' ? 'process.code_signature' : 'process.Ext.code_signature', + operator: 'included', + type: 'match', + value: signature, + }, + ], + }, + ], }, - }, - })) - ); + })); + } else { + return filePaths.map((filePath) => ({ + ...common, + remediation: { + ...common.remediation, + exception_list_items: [ + { + ...common.remediation.exception_list_items[0], + entries: [ + { + field: 'process.executable.caseless', + operator: 'included', + type: 'match', + value: filePath, + }, + ], + }, + ], + }, + })); + } + }); }); } From 7d55f7c61aaafe3a8e6052c7c8821b772204ce2b Mon Sep 17 00:00:00 2001 From: "konrad.szwarc" Date: Thu, 2 Jan 2025 15:32:43 +0100 Subject: [PATCH 2/3] cleanup --- .../defend_insights/common_attributes.gen.ts | 4 +- .../common_attributes.schema.yaml | 4 +- .../insights/workflow_insights_results.tsx | 3 +- .../assistant/tools/defend_insights/index.ts | 16 +- .../output_parsers/incompatible_antivirus.ts | 2 +- .../prompts/incompatible_antivirus.ts | 2 +- .../builders/incompatible_antivirus.test.ts | 198 +++++++++++------- .../builders/incompatible_antivirus.ts | 135 +++++------- 8 files changed, 187 insertions(+), 177 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.gen.ts index 55223cb7c7617..73d8f658cf384 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.gen.ts @@ -37,9 +37,9 @@ export const DefendInsightEvent = z.object({ */ value: z.string(), /** - * The signature of the event + * The signer's ID */ - signature: z.string().optional(), + signerId: z.string().optional(), }); /** diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.schema.yaml index d928d855cdc19..60a11d201f24c 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/defend_insights/common_attributes.schema.yaml @@ -23,8 +23,8 @@ components: value: description: The value of the event type: string - signature: - description: The signature of the event + signerId: + description: The signer's ID type: string DefendInsightType: diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_results.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_results.tsx index 9b28e2a2e9add..c5d77bb26b4ee 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_results.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_results.tsx @@ -122,7 +122,8 @@ export const WorkflowInsightsResults = ({ {insight.message} - {item.entries[0].field === 'process.executable.caseless' && + {item.entries[0].type === 'match' && + item.entries[0].field === 'process.executable.caseless' && item.entries[0].value} diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/index.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/index.ts index 8ed3fac8ba32d..d09e8d8f8c8a7 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/index.ts @@ -105,21 +105,13 @@ export const DEFEND_INSIGHTS_TOOL: AssistantTool = Object.freeze({ outputParser: outputFixingParser, }); - // console.log(JSON.stringify(prompt, null, 2)); - - const query = getDefendInsightsPrompt({ - type: insightType, - events: anonymizedEvents, - }); - // console.log(JSON.stringify(query, null, 2)); - const result = await answerFormattingChain.call({ - query, + query: getDefendInsightsPrompt({ + type: insightType, + events: anonymizedEvents, + }), timeout: langChainTimeout, }); - - // console.log(JSON.stringify(result, null, 2)); - const insights: DefendInsight[] = result.records; await securityWorkflowInsightsService.createFromDefendInsights(insights, request); diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts index 3c37667637269..e256f59ab8710 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts @@ -18,7 +18,7 @@ export function getIncompatibleVirusOutputParser() { .object({ id: z.string().describe('The event ID'), endpointId: z.string().describe('The endpoint ID'), - signature: z + signerId: z .string() .optional() .describe('The process.code_signature.signing_id value of the event, if present'), diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts index 0ead14f0b51b3..931ed8b2b416a 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts @@ -6,7 +6,7 @@ */ export function getIncompatibleAntivirusPrompt({ events }: { events: string[] }): string { - return `You are an Elastic Security user tasked with analyzing file events from Elastic Security to identify antivirus processes. Only focus on detecting antivirus processes. Ignore processes that belong to Elastic Agent or Elastic Defend, that are not antivirus processes, or are typical processes built into the operating system. Accuracy is of the utmost importance, try to minimize false positives. Group the processes by the antivirus program, keeping track of the agent.id and _id associated to each of the individual events as endpointId and eventId respectively. If there are no events, ignore the group field. If the process.code_signature.signing_id field is present in an event, set its value in the signature field. If the process.code_signature.signing_id field is absent, ignore the signature field. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. + return `You are an Elastic Security user tasked with analyzing file events from Elastic Security to identify antivirus processes. Only focus on detecting antivirus processes. Ignore processes that belong to Elastic Agent or Elastic Defend, that are not antivirus processes, or are typical processes built into the operating system. Accuracy is of the utmost importance, try to minimize false positives. Group the processes by the antivirus program, keeping track of the agent.id and _id associated to each of the individual events as endpointId and eventId respectively. If there are no events, ignore the group field. If the process.code_signature.signing_id field is present in an event, set its value in the signerId field. If the process.code_signature.signing_id field is absent, ignore the signerId field. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. Use context from the following process events to provide insights: """ diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.test.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.test.ts index 41b833be05fc0..47d8f2b139ee9 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.test.ts @@ -30,104 +30,140 @@ jest.mock('../helpers', () => ({ })); describe('buildIncompatibleAntivirusWorkflowInsights', () => { - let params: BuildWorkflowInsightParams; + const mockEndpointAppContextService = createMockEndpointAppContext().service; + mockEndpointAppContextService.getEndpointMetadataService = jest.fn().mockReturnValue({ + getMetadataForEndpoints: jest.fn(), + }); + const endpointMetadataService = + mockEndpointAppContextService.getEndpointMetadataService() as jest.Mocked; - beforeEach(() => { - const mockEndpointAppContextService = createMockEndpointAppContext().service; - mockEndpointAppContextService.getEndpointMetadataService = jest.fn().mockReturnValue({ - getMetadataForEndpoints: jest.fn(), - }); - const endpointMetadataService = - mockEndpointAppContextService.getEndpointMetadataService() as jest.Mocked; - - params = { - defendInsights: [ - { - group: 'AVGAntivirus', - events: [ - { - id: 'lqw5opMB9Ke6SNgnxRSZ', - endpointId: 'f6e2f338-6fb7-4c85-9c23-d20e9f96a051', - value: '/Applications/AVGAntivirus.app/Contents/Backend/services/com.avg.activity', - }, - ], + const generateParams = (signerId?: string): BuildWorkflowInsightParams => ({ + defendInsights: [ + { + group: 'AVGAntivirus', + events: [ + { + id: 'lqw5opMB9Ke6SNgnxRSZ', + endpointId: 'f6e2f338-6fb7-4c85-9c23-d20e9f96a051', + value: '/Applications/AVGAntivirus.app/Contents/Backend/services/com.avg.activity', + ...(signerId ? { signerId } : {}), + }, + ], + }, + ], + request: { + body: { + insightType: 'incompatible_antivirus', + endpointIds: ['endpoint-1'], + apiConfig: { + connectorId: 'connector-id-1', + actionTypeId: 'action-type-id-1', + model: 'model-1', }, - ], - request: { - body: { - insightType: 'incompatible_antivirus', - endpointIds: ['endpoint-1'], - apiConfig: { - connectorId: 'connector-id-1', - actionTypeId: 'action-type-id-1', - model: 'model-1', + anonymizationFields: [], + subAction: 'invokeAI', + }, + } as unknown as KibanaRequest, + endpointMetadataService, + }); + + const buildExpectedInsight = (os: string, field: string, value: string) => + expect.objectContaining({ + '@timestamp': expect.any(moment), + message: 'Incompatible antiviruses detected', + category: Category.Endpoint, + type: 'incompatible_antivirus', + source: { + type: SourceType.LlmConnector, + id: 'connector-id-1', + data_range_start: expect.any(moment), + data_range_end: expect.any(moment), + }, + target: { + type: TargetType.Endpoint, + ids: ['endpoint-1'], + }, + action: { + type: ActionType.Refreshed, + timestamp: expect.any(moment), + }, + value: 'AVGAntivirus', + remediation: { + exception_list_items: [ + { + list_id: ENDPOINT_ARTIFACT_LISTS.trustedApps.id, + name: 'AVGAntivirus', + description: 'Suggested by Security Workflow Insights', + entries: [ + { + field, + operator: 'included', + type: 'match', + value, + }, + ], + tags: ['policy:all'], + os_types: [os], }, - anonymizationFields: [], - subAction: 'invokeAI', + ], + }, + metadata: { + notes: { + llm_model: 'model-1', }, - } as unknown as KibanaRequest, - endpointMetadataService, - }; + }, + }); + it('should correctly build workflow insights', async () => { (groupEndpointIdsByOS as jest.Mock).mockResolvedValue({ windows: ['endpoint-1'], }); + const params = generateParams(); + const result = await buildIncompatibleAntivirusWorkflowInsights(params); + + expect(result).toEqual([ + buildExpectedInsight( + 'windows', + 'process.executable.caseless', + '/Applications/AVGAntivirus.app/Contents/Backend/services/com.avg.activity' + ), + ]); + expect(groupEndpointIdsByOS).toHaveBeenCalledWith( + ['endpoint-1'], + params.endpointMetadataService + ); }); - it('should correctly build workflow insights', async () => { + it('should correctly build workflow insights for Windows with signerId provided', async () => { + (groupEndpointIdsByOS as jest.Mock).mockResolvedValue({ + windows: ['endpoint-1'], + }); + const params = generateParams('test.com'); + const result = await buildIncompatibleAntivirusWorkflowInsights(params); expect(result).toEqual([ - expect.objectContaining({ - '@timestamp': expect.any(moment), - message: 'Incompatible antiviruses detected', - category: Category.Endpoint, - type: 'incompatible_antivirus', - source: { - type: SourceType.LlmConnector, - id: 'connector-id-1', - data_range_start: expect.any(moment), - data_range_end: expect.any(moment), - }, - target: { - type: TargetType.Endpoint, - ids: ['endpoint-1'], - }, - action: { - type: ActionType.Refreshed, - timestamp: expect.any(moment), - }, - value: 'AVGAntivirus', - remediation: { - exception_list_items: [ - { - list_id: ENDPOINT_ARTIFACT_LISTS.trustedApps.id, - name: 'AVGAntivirus', - description: 'Suggested by Security Workflow Insights', - entries: [ - { - field: 'process.executable.caseless', - operator: 'included', - type: 'match', - value: - '/Applications/AVGAntivirus.app/Contents/Backend/services/com.avg.activity', - }, - ], - tags: ['policy:all'], - os_types: ['windows'], - }, - ], - }, - metadata: { - notes: { - llm_model: 'model-1', - }, - }, - }), + buildExpectedInsight('windows', 'process.Ext.code_signature', 'test.com'), ]); expect(groupEndpointIdsByOS).toHaveBeenCalledWith( ['endpoint-1'], params.endpointMetadataService ); }); + + it('should correctly build workflow insights for MacOS with signerId provided', async () => { + (groupEndpointIdsByOS as jest.Mock).mockResolvedValue({ + macos: ['endpoint-1'], + }); + + const params = generateParams('test.com'); + + const result = await buildIncompatibleAntivirusWorkflowInsights(params); + + expect(result).toEqual([buildExpectedInsight('macos', 'process.code_signature', 'test.com')]); + expect(groupEndpointIdsByOS).toHaveBeenCalledWith( + ['endpoint-1'], + params.endpointMetadataService + ); + }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.ts index 0c38079d891fc..21f64f612779f 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/workflow_insights/builders/incompatible_antivirus.ts @@ -31,94 +31,75 @@ export async function buildIncompatibleAntivirusWorkflowInsights( const { insightType, endpointIds, apiConfig } = request.body; const osEndpointIdsMap = await groupEndpointIdsByOS(endpointIds, endpointMetadataService); + return defendInsights.flatMap((defendInsight: DefendInsight) => { const filePaths = Array.from(new Set((defendInsight.events ?? []).map((event) => event.value))); const signatures = Array.from( - new Set((defendInsight.events ?? []).map((event) => event.signature)) + new Set((defendInsight.events ?? []).map((event) => event.signerId)) ).filter(Boolean) as string[]; - return Object.keys(osEndpointIdsMap).flatMap((os) => { - const common = { - '@timestamp': currentTime, - // TODO add i18n support - message: 'Incompatible antiviruses detected', - category: Category.Endpoint, - type: insightType, - source: { - type: SourceType.LlmConnector, - id: apiConfig.connectorId, - // TODO use actual time range when we add support - data_range_start: currentTime, - data_range_end: currentTime.clone().add(24, 'hours'), - }, - target: { - type: TargetType.Endpoint, - ids: endpointIds, - }, - action: { - type: ActionType.Refreshed, - timestamp: currentTime, + const createRemediation = (field: string, value: string, os: string) => ({ + '@timestamp': currentTime, + // TODO add i18n support + message: 'Incompatible antiviruses detected', + category: Category.Endpoint, + type: insightType, + source: { + type: SourceType.LlmConnector, + id: apiConfig.connectorId, + // TODO use actual time range when we add support + data_range_start: currentTime, + data_range_end: currentTime.clone().add(24, 'hours'), + }, + target: { + type: TargetType.Endpoint, + ids: endpointIds, + }, + action: { + type: ActionType.Refreshed, + timestamp: currentTime, + }, + value: defendInsight.group, + metadata: { + notes: { + llm_model: apiConfig.model ?? '', }, - value: defendInsight.group, - metadata: { - notes: { - llm_model: apiConfig.model ?? '', - }, - }, - remediation: { - exception_list_items: [ - { - list_id: ENDPOINT_ARTIFACT_LISTS.trustedApps.id, - name: defendInsight.group, - description: 'Suggested by Security Workflow Insights', - entries: [], - // TODO add per policy support - tags: ['policy:all'], - os_types: [os as SupportedHostOsType], - }, - ], - }, - }; - if (signatures.length) { - return signatures.map((signature) => ({ - ...common, - remediation: { - ...common.remediation, - exception_list_items: [ + }, + remediation: { + exception_list_items: [ + { + list_id: ENDPOINT_ARTIFACT_LISTS.trustedApps.id, + name: defendInsight.group, + description: 'Suggested by Security Workflow Insights', + entries: [ { - ...common.remediation.exception_list_items[0], - entries: [ - { - field: os === 'macos' ? 'process.code_signature' : 'process.Ext.code_signature', - operator: 'included', - type: 'match', - value: signature, - }, - ], + field, + operator: 'included' as const, + type: 'match' as const, + value, }, ], + // TODO add per policy support + tags: ['policy:all'], + os_types: [os as SupportedHostOsType], }, - })); + ], + }, + }); + + return Object.keys(osEndpointIdsMap).flatMap((os) => { + if (signatures.length) { + return signatures.map((signature) => + createRemediation( + os === 'macos' ? 'process.code_signature' : 'process.Ext.code_signature', + signature, + os + ) + ); } else { - return filePaths.map((filePath) => ({ - ...common, - remediation: { - ...common.remediation, - exception_list_items: [ - { - ...common.remediation.exception_list_items[0], - entries: [ - { - field: 'process.executable.caseless', - operator: 'included', - type: 'match', - value: filePath, - }, - ], - }, - ], - }, - })); + return filePaths.map((filePath) => + createRemediation('process.executable.caseless', filePath, os) + ); } }); }); From 07a50b881280ec60a2d2f28760c7eb6d12e8ace5 Mon Sep 17 00:00:00 2001 From: "konrad.szwarc" Date: Thu, 2 Jan 2025 17:19:49 +0100 Subject: [PATCH 3/3] cleanup --- .../kbn-elastic-assistant-common/impl/capabilities/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/capabilities/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/capabilities/index.ts index 623810f956ada..35f4b88ef7174 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/capabilities/index.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/capabilities/index.ts @@ -21,5 +21,5 @@ export type AssistantFeatureKey = keyof AssistantFeatures; export const defaultAssistantFeatures = Object.freeze({ assistantModelEvaluation: false, attackDiscoveryAlertFiltering: false, - defendInsights: true, + defendInsights: false, });