[Security Assistant] Context prompts (#224956)

This commit is contained in:
Steph Milovic 2025-06-24 20:32:51 -06:00 committed by GitHub
parent bf0003f3be
commit 2e384fb0a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 1263 additions and 116 deletions

View file

@ -67,6 +67,12 @@ export const ELASTIC_AI_ASSISTANT_ALERT_SUMMARY_URL_BULK_ACTION =
export const ELASTIC_AI_ASSISTANT_ALERT_SUMMARY_URL_FIND =
`${ELASTIC_AI_ASSISTANT_ALERT_SUMMARY_URL}/_find` as const;
// Security AI Prompts (prompt integration)
export const ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL =
`${ELASTIC_AI_ASSISTANT_URL}/security_ai_prompts` as const;
export const ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND =
`${ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL}/_find` as const;
// Defend insights
export const DEFEND_INSIGHTS_ID = 'defend-insights';
export const DEFEND_INSIGHTS = `${ELASTIC_AI_ASSISTANT_INTERNAL_URL}/defend_insights`;

View file

@ -102,7 +102,7 @@ export const BulkActionBase = z.object({
});
/**
* User screen context.
* IDs for a specific prompt within a group of prompts.
*/
export type PromptIds = z.infer<typeof PromptIds>;
export const PromptIds = z.object({

View file

@ -101,7 +101,7 @@ components:
- "5678"
PromptIds:
description: User screen context.
description: IDs for a specific prompt within a group of prompts.
type: object
required:
- promptId

View file

@ -75,4 +75,6 @@ export * from './prompts/find_prompts_route.gen';
export * from './alert_summary/bulk_crud_alert_summary_route.gen';
export * from './alert_summary/find_alert_summary_route.gen';
export * from './security_ai_prompts/find_prompts_route.gen';
export { PromptResponse, PromptTypeEnum } from './prompts/bulk_crud_prompts_route.gen';

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Common Security AI Prompts Attributes
* version: not applicable
*/
import { z } from '@kbn/zod';
export type PromptItem = z.infer<typeof PromptItem>;
export const PromptItem = z.object({
promptId: z.string(),
prompt: z.string(),
});
/**
* Prompt array by prompt group id and prompt id.
*/
export type PromptItemArray = z.infer<typeof PromptItemArray>;
export const PromptItemArray = z.array(PromptItem);

View file

@ -0,0 +1,26 @@
openapi: 3.0.0
info:
title: Common Security AI Prompts Attributes
version: 'not applicable'
paths: {}
components:
x-codegen-enabled: true
schemas:
PromptItem:
type: object
properties:
promptId:
type: string
example: systemPrompt
prompt:
type: string
example: This is the prompt
required:
- promptId
- prompt
PromptItemArray:
type: array
description: Prompt array by prompt group id and prompt id.
items:
$ref: '#/components/schemas/PromptItem'

View file

@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Get Security AI Prompt endpoint
* version: 2023-10-31
*/
import { z } from '@kbn/zod';
import { ArrayFromString } from '@kbn/zod-helpers';
import { PromptItemArray } from './common_attributes.gen';
export type FindSecurityAIPromptsRequestQuery = z.infer<typeof FindSecurityAIPromptsRequestQuery>;
export const FindSecurityAIPromptsRequestQuery = z.object({
/**
* Connector id used for prompt lookup
*/
connector_id: z.string().optional(),
/**
* The unique identifier for the prompt group
*/
prompt_group_id: z.string(),
/**
* Comma-separated list of prompt IDs to retrieve
*/
prompt_ids: ArrayFromString(z.string()),
});
export type FindSecurityAIPromptsRequestQueryInput = z.input<
typeof FindSecurityAIPromptsRequestQuery
>;
export type FindSecurityAIPromptsResponse = z.infer<typeof FindSecurityAIPromptsResponse>;
export const FindSecurityAIPromptsResponse = z.object({
prompts: PromptItemArray,
});

View file

@ -0,0 +1,63 @@
openapi: 3.0.0
info:
title: Get Security AI Prompt endpoint
version: '2023-10-31'
paths:
/api/security_ai_assistant/security_ai_prompts/_find:
get:
x-codegen-enabled: true
x-internal: true
x-labels: [ess, serverless]
operationId: FindSecurityAIPrompts
description: Gets Security AI prompts
summary: Gets Security AI prompts
tags:
- Security AI Prompts API
parameters:
- name: connector_id
in: query
description: Connector id used for prompt lookup
required: false
schema:
type: string
- name: prompt_group_id
in: query
description: The unique identifier for the prompt group
required: true
schema:
type: string
- name: prompt_ids
in: query
description: Comma-separated list of prompt IDs to retrieve
required: true
style: form
explode: true
schema:
type: array
items:
type: string
responses:
'200':
description: Indicates a successful call.
content:
application/json:
schema:
type: object
properties:
prompts:
$ref: './common_attributes.schema.yaml#/components/schemas/PromptItemArray'
required:
- prompts
'400':
description: Generic Error
content:
application/json:
schema:
type: object
properties:
status_code:
type: number
error:
type: string
message:
type: string

View file

@ -0,0 +1,97 @@
/*
* 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 { waitFor, renderHook } from '@testing-library/react';
import { useFindPrompts, UseFindPromptsParams } from './use_find_prompts';
import {
API_VERSIONS,
ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
} from '@kbn/elastic-assistant-common';
import { TestProviders } from '../../../mock/test_providers/test_providers';
import { IToasts } from '@kbn/core-notifications-browser';
const mockHttpFetch = jest.fn();
const mockToasts = {
addSuccess: jest.fn(),
addError: jest.fn(),
} as unknown as IToasts;
describe('useFindPrompts', () => {
const params: UseFindPromptsParams = {
context: {
isAssistantEnabled: true,
httpFetch: mockHttpFetch,
toasts: mockToasts,
},
params: {
connector_id: 'connector-1',
prompt_ids: ['prompt-1'],
prompt_group_id: 'group-1',
},
signal: undefined,
};
beforeEach(() => {
jest.clearAllMocks();
});
it('returns initial and placeholder data when loading', async () => {
mockHttpFetch.mockResolvedValueOnce({ prompts: [{ id: 'prompt-1', name: 'Prompt 1' }] });
const { result } = renderHook(() => useFindPrompts(params), {
wrapper: TestProviders,
});
expect(result.current.data).toEqual({ prompts: [] });
await waitFor(() => result.current.isSuccess);
await waitFor(() =>
expect(result.current.data).toEqual({ prompts: [{ id: 'prompt-1', name: 'Prompt 1' }] })
);
});
it('disables the query if isAssistantEnabled is false', async () => {
const disabledParams = { ...params, context: { ...params.context, isAssistantEnabled: false } };
const { result } = renderHook(() => useFindPrompts(disabledParams), { wrapper: TestProviders });
expect(result.current.isLoading).toBe(false);
expect(result.current.data).toEqual({ prompts: [] });
expect(mockHttpFetch).not.toHaveBeenCalled();
});
it('calls httpFetch with correct arguments', async () => {
mockHttpFetch.mockResolvedValueOnce({ prompts: [{ id: 'prompt-1', name: 'Prompt 1' }] });
const { result } = renderHook(() => useFindPrompts(params), {
wrapper: TestProviders,
});
await waitFor(() => result.current.isSuccess);
expect(mockHttpFetch).toHaveBeenCalledWith(
ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
expect.objectContaining({
method: 'GET',
version: API_VERSIONS.public.v1,
query: {
connector_id: 'connector-1',
prompt_ids: ['prompt-1'],
prompt_group_id: 'group-1',
},
})
);
});
it('shows a toast on error', async () => {
const error = { body: { message: 'Something went wrong' } };
mockHttpFetch.mockRejectedValueOnce(error);
const { result } = renderHook(() => useFindPrompts(params), {
wrapper: TestProviders,
});
await waitFor(() => result.current.isError);
expect(mockToasts.addError).toHaveBeenCalledWith(
expect.any(Error),
expect.objectContaining({
title: expect.any(String),
})
);
});
});

View file

@ -0,0 +1,107 @@
/*
* 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 { useQuery } from '@tanstack/react-query';
import { HttpHandler, IToasts } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import {
API_VERSIONS,
ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
FindSecurityAIPromptsRequestQuery,
FindSecurityAIPromptsResponse,
} from '@kbn/elastic-assistant-common';
export interface UseFindPromptsParams {
context: {
isAssistantEnabled: boolean;
httpFetch: HttpHandler;
toasts: IToasts;
};
signal?: AbortSignal | undefined;
params: FindSecurityAIPromptsRequestQuery;
}
/**
* API call for fetching prompts for current spaceId
*
* @param {Object} options - The options object.
* @param {string} options.consumer - prompt consumer
* @param {AbortSignal} [options.signal] - AbortSignal
*
* @returns {useQuery} hook for getting the status of the prompts
*/
export const useFindPrompts = (payload: UseFindPromptsParams) => {
const { isAssistantEnabled, httpFetch, toasts } = payload.context;
const QUERY = {
connector_id: payload.params.connector_id,
prompt_ids: payload.params.prompt_ids,
prompt_group_id: payload.params.prompt_group_id,
};
const CACHING_KEYS = [
ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
QUERY.connector_id,
QUERY.prompt_ids,
QUERY.prompt_group_id,
API_VERSIONS.public.v1,
];
return useQuery<FindSecurityAIPromptsResponse, unknown, FindSecurityAIPromptsResponse>(
CACHING_KEYS,
async () =>
getPrompts({
httpFetch,
signal: payload.signal,
toasts,
query: QUERY,
}),
{
initialData: {
prompts: [],
},
placeholderData: {
prompts: [],
},
keepPreviousData: true,
refetchOnWindowFocus: false,
enabled: isAssistantEnabled,
}
);
};
const getPrompts = async ({
httpFetch,
signal,
toasts,
query,
}: {
httpFetch: HttpHandler;
toasts: IToasts;
signal?: AbortSignal | undefined;
query: FindSecurityAIPromptsRequestQuery;
}) => {
try {
return await httpFetch<FindSecurityAIPromptsResponse>(
ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
{
method: 'GET',
version: API_VERSIONS.public.v1,
signal,
query,
}
);
} catch (error) {
toasts?.addError(error.body && error.body.message ? new Error(error.body.message) : error, {
title: i18n.translate('xpack.elasticAssistant.securityAiPrompts.getPromptsError', {
defaultMessage: 'Error fetching Security AI Prompts',
}),
});
throw error;
}
};

View file

@ -184,6 +184,8 @@ export { getCombinedMessage } from './impl/assistant/prompt/helpers';
export { useChatComplete } from './impl/assistant/api/chat_complete/use_chat_complete';
export { useFetchAnonymizationFields } from './impl/assistant/api/anonymization_fields/use_fetch_anonymization_fields';
export { useFindPrompts } from './impl/assistant/api/security_ai_prompts/use_find_prompts';
export interface UseAssistantAvailability {
// True when searchAiLake configurations is available
hasSearchAILakeConfigurations: boolean;

View file

@ -7529,7 +7529,6 @@
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityDashboardConversationId": "Tableau de bord de Qualité des données",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPill": "Qualité des données ({indexName})",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPillTooltip": "Ajoutez ce rapport de Qualité des données comme contexte",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt": "Expliquez les résultats ci-dessus et donnez des options pour résoudre les incompatibilités.",
"securitySolutionPackages.ecsDataQualityDashboard.defaultPanelTitle": "Vérifier les mappings d'index",
"securitySolutionPackages.ecsDataQualityDashboard.detectionEngineRulesWontWorkMessage": "❌ Les règles de moteur de détection référençant ces champs ne leur correspondront peut-être pas correctement",
"securitySolutionPackages.ecsDataQualityDashboard.docs": "Documents",
@ -16363,6 +16362,13 @@
"xpack.elasticAssistant.prompts.getPromptsError": "Erreur lors de la récupération des invites",
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.enterprisePrompt": "L'assistant d'IA d'Elastic n'est accessible qu'aux entreprises. Veuillez mettre votre licence à niveau pour bénéficier de cette fonctionnalité.",
"xpack.elasticAssistantPlugin.assistant.apiErrorTitle": "Une erreur sest produite lors de lenvoi de votre message.",
"xpack.elasticAssistantPlugin.assistant.contentReferences.knowledgeBaseEntryReference.label": "Entrée de la base de connaissances",
"xpack.elasticAssistantPlugin.assistant.contentReferences.securityAlertReference.label": "Afficher l'alerte",
"xpack.elasticAssistantPlugin.assistant.contentReferences.securityAlertsPageReference.label": "Afficher les alertes",
"xpack.elasticAssistantPlugin.assistant.getComments.assistant": "Assistant",
"xpack.elasticAssistantPlugin.assistant.getComments.at": "à : {timestamp}",
"xpack.elasticAssistantPlugin.assistant.getComments.system": "Système",
"xpack.elasticAssistantPlugin.assistant.getComments.you": "Vous",
"xpack.elasticAssistantPlugin.assistant.quickPrompts.esqlQueryGenerationTitle": "Génération de requête ES|QL",
"xpack.elasticAssistantPlugin.assistant.title": "Assistant d'IA d'Elastic",
"xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxGenerationAttemptsErrorMessage": "Nombre maximum de tentatives de génération ({generationAttempts}) atteint. Essayez d'envoyer un nombre d'alertes moins élevé à ce modèle.",
@ -35056,8 +35062,7 @@
"xpack.securitySolution.assistant.commentActions.viewAPMTraceLabel": "Voir la trace APM pour ce message",
"xpack.securitySolution.assistant.content.promptContexts.indexTitle": "index",
"xpack.securitySolution.assistant.content.promptContexts.viewTitle": "vue",
"xpack.securitySolution.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown": "Ajoutez votre description, les actions que vous recommandez ainsi que les étapes de triage à puces. Utilisez les données \"MITRE ATT&CK\" fournies pour ajouter du contexte et des recommandations de MITRE ainsi que des liens hypertexte vers les pages pertinentes sur le site web de MITRE. Assurez-vous d'inclure les scores de risque de l'utilisateur et de l'hôte du contexte. Votre réponse doit inclure des étapes qui pointent vers les fonctionnalités spécifiques d'Elastic Security, y compris les actions de réponse du terminal, l'intégration OSQuery Manager d'Elastic Agent (avec des exemples de requêtes OSQuery), des analyses de timeline et d'entités, ainsi qu'un lien pour toute la documentation Elastic Security pertinente.",
"xpack.securitySolution.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries": "Évaluer lévénement depuis le contexte ci-dessus et formater soigneusement la sortie en syntaxe Markdown pour mon cas Elastic Security.",
"xpack.securitySolution.assistant.conversationMigrationStatus.title": "Les conversations de stockage local ont été persistées avec succès.",
"xpack.securitySolution.assistant.quickPrompts.alertSummarizationPrompt": "En tant quexpert en opérations de sécurité et en réponses aux incidents, décomposer lalerte jointe et résumer ce quelle peut impliquer pour mon organisation.",
"xpack.securitySolution.assistant.quickPrompts.alertSummarizationTitle": "Synthèse de lalerte",
"xpack.securitySolution.assistant.quickPrompts.AutomationPrompt": "Quelle intégration dElastic Agent activée par Fleet dois-je utiliser pour collecter des logs et des évènements de :",
@ -37002,7 +37007,6 @@
"xpack.securitySolution.detectionEngine.ruleManagement.createRule.threatMappingField.requiredError": "Au moins une correspondance d'indicateur est requise.",
"xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesConversationId": "Règles de détection",
"xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesCreateEditFormConversationId": "Formulaire de création des règles de détection",
"xpack.securitySolution.detectionEngine.ruleManagement.explainThenSummarizeRuleDetails": "Veuillez expliquer les règles sélectionnées ci-dessus. Pour chaque règle : indiquez pourquoi la règle est pertinente, la requête telle qu'elle est publiée dans le référentiel de règles de détection d'Elastic, ainsi qu'une explication approfondie de celle-ci et ce qu'elle implique généralement pour une organisation si elle est détectée.",
"xpack.securitySolution.detectionEngine.ruleManagement.fields.from.helpText": "Expression mathématique de datage, par ex. \"now\", \"now-3d\", \"now+2m\".",
"xpack.securitySolution.detectionEngine.ruleManagement.fields.from.label": "De",
"xpack.securitySolution.detectionEngine.ruleManagement.fields.interval.helpText": "Les règles s'exécutent de façon régulière et détectent les alertes dans la période de temps spécifiée.",

View file

@ -7529,7 +7529,6 @@
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityDashboardConversationId": "データ品質ダッシュボード",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPill": "データ品質({indexName}",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPillTooltip": "このデータ品質レポートをコンテキストとして追加",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt": "上記の結果を説明し、一部のオプションを記述して非互換性を修正します。",
"securitySolutionPackages.ecsDataQualityDashboard.defaultPanelTitle": "インデックスマッピングの確認",
"securitySolutionPackages.ecsDataQualityDashboard.detectionEngineRulesWontWorkMessage": "❌ これらのフィールドを参照する検出エンジンルールが正常に一致しない場合がある",
"securitySolutionPackages.ecsDataQualityDashboard.docs": "ドキュメント",
@ -16381,6 +16380,13 @@
"xpack.elasticAssistant.prompts.getPromptsError": "プロンプト取得エラー",
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.enterprisePrompt": "Elastic AI Assistantはエンタープライズユーザーのみご利用いただけます。 この機能を使用するには、ライセンスをアップグレードしてください。",
"xpack.elasticAssistantPlugin.assistant.apiErrorTitle": "メッセージの送信中にエラーが発生しました。",
"xpack.elasticAssistantPlugin.assistant.contentReferences.knowledgeBaseEntryReference.label": "ナレッジベースエントリ",
"xpack.elasticAssistantPlugin.assistant.contentReferences.securityAlertReference.label": "アラートを表示",
"xpack.elasticAssistantPlugin.assistant.contentReferences.securityAlertsPageReference.label": "アラートを表示",
"xpack.elasticAssistantPlugin.assistant.getComments.assistant": "アシスタント",
"xpack.elasticAssistantPlugin.assistant.getComments.at": "at: {timestamp}",
"xpack.elasticAssistantPlugin.assistant.getComments.system": "システム",
"xpack.elasticAssistantPlugin.assistant.getComments.you": "あなた",
"xpack.elasticAssistantPlugin.assistant.quickPrompts.esqlQueryGenerationTitle": "ES|QLクエリ生成",
"xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxGenerationAttemptsErrorMessage": "最大生成試行回数({generationAttempts})に達しました。このモデルに送信するアラートの数を減らしてください。",
"xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxHallucinationFailuresErrorMessage": "最大ハルシネーション失敗回数({hallucinationFailures})に達しました。このモデルに送信するアラートの数を減らしてください。",
@ -35090,8 +35096,7 @@
"xpack.securitySolution.assistant.commentActions.viewAPMTraceLabel": "このメッセージのAPMトレースを表示",
"xpack.securitySolution.assistant.content.promptContexts.indexTitle": "インデックス",
"xpack.securitySolution.assistant.content.promptContexts.viewTitle": "表示",
"xpack.securitySolution.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown": "説明、推奨されるアクション、箇条書きのトリアージステップを追加します。提供されたMITRE ATT&CKデータを使用して、MITREからのコンテキストや推奨事項を追加し、MITREのWebサイトの関連ページにハイパーリンクを貼ります。コンテキストのユーザーとホストのリスクスコアデータを必ず含めてください。回答には、エンドポイント対応アクション、ElasticエージェントOSQueryマネージャー統合osqueryクエリーの例を付けて、タイムライン、エンティティ分析など、Elasticセキュリティ固有の機能を指す手順を含め、関連するElasticセキュリティのドキュメントすべてにリンクしてください。",
"xpack.securitySolution.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries": "上記のコンテキストからイベントを評価し、Elasticセキュリティのケース用に、出力をマークダウン構文で正しく書式設定してください。",
"xpack.securitySolution.assistant.conversationMigrationStatus.title": "ローカルストレージ会話は正常に永続しました。",
"xpack.securitySolution.assistant.quickPrompts.alertSummarizationPrompt": "セキュリティ運用とインシデント対応のエキスパートとして、添付されたアラートの内訳を説明し、それが私の組織にとって何を意味するのかを要約してください。",
"xpack.securitySolution.assistant.quickPrompts.alertSummarizationTitle": "アラート要約",
"xpack.securitySolution.assistant.quickPrompts.AutomationPrompt": "ログやイベントの収集には、どのFleet対応Elasticエージェント統合を使用すべきですか。",
@ -37039,7 +37044,6 @@
"xpack.securitySolution.detectionEngine.ruleManagement.createRule.threatMappingField.requiredError": "1 つ以上のインジケーター一致が必要です。",
"xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesConversationId": "検出ルール",
"xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesCreateEditFormConversationId": "検出ルール作成フォーム",
"xpack.securitySolution.detectionEngine.ruleManagement.explainThenSummarizeRuleDetails": "上記の選択したルールを説明してください。各ルールについて、関連する理由、Elasticの検出ルールリポジトリで公開されているクエリとその詳細な説明、検出された場合の組織にとっての一般的な意味を強調します。",
"xpack.securitySolution.detectionEngine.ruleManagement.fields.from.helpText": "日付演算式。例now、now-3d、now+2mなど。",
"xpack.securitySolution.detectionEngine.ruleManagement.fields.from.label": "開始:",
"xpack.securitySolution.detectionEngine.ruleManagement.fields.interval.helpText": "ルールを定期的に実行し、指定の時間枠内でアラートを検出します。",

View file

@ -7524,7 +7524,6 @@
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityDashboardConversationId": "数据质量仪表板",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPill": "数据质量 ({indexName})",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPillTooltip": "将此数据质量报告添加为上下文",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt": "解释上述结果,并说明某些选项以解决不兼容问题。",
"securitySolutionPackages.ecsDataQualityDashboard.defaultPanelTitle": "检查索引映射",
"securitySolutionPackages.ecsDataQualityDashboard.detectionEngineRulesWontWorkMessage": "❌ 引用这些字段的检测引擎规则可能无法与其正确匹配",
"securitySolutionPackages.ecsDataQualityDashboard.docs": "文档",
@ -16377,6 +16376,13 @@
"xpack.elasticAssistant.prompts.getPromptsError": "提取提示时出错",
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.enterprisePrompt": "Elastic AI 助手仅对企业用户可用。请升级许可证以使用此功能。",
"xpack.elasticAssistantPlugin.assistant.apiErrorTitle": "发送消息时出错。",
"xpack.elasticAssistantPlugin.assistant.contentReferences.knowledgeBaseEntryReference.label": "知识库条目",
"xpack.elasticAssistantPlugin.assistant.contentReferences.securityAlertReference.label": "查看告警",
"xpack.elasticAssistantPlugin.assistant.contentReferences.securityAlertsPageReference.label": "查看告警",
"xpack.elasticAssistantPlugin.assistant.getComments.assistant": "助手",
"xpack.elasticAssistantPlugin.assistant.getComments.at": "时间:{timestamp}",
"xpack.elasticAssistantPlugin.assistant.getComments.system": "系统",
"xpack.elasticAssistantPlugin.assistant.getComments.you": "您",
"xpack.elasticAssistantPlugin.assistant.quickPrompts.esqlQueryGenerationTitle": "ES|QL 查询生成",
"xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxGenerationAttemptsErrorMessage": "已达到最大生成尝试次数 ({generationAttempts})。尝试向此模型发送更少的告警。",
"xpack.elasticAssistantPlugin.attackDiscovery.defaultAttackDiscoveryGraph.nodes.retriever.helpers.throwIfErrorCountsExceeded.maxHallucinationFailuresErrorMessage": "已达到最大幻觉失败次数 ({hallucinationFailures})。尝试向此模型发送更少的告警。",
@ -35079,8 +35085,7 @@
"xpack.securitySolution.assistant.commentActions.viewAPMTraceLabel": "查看此消息的 APM 跟踪",
"xpack.securitySolution.assistant.content.promptContexts.indexTitle": "索引",
"xpack.securitySolution.assistant.content.promptContexts.viewTitle": "视图",
"xpack.securitySolution.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown": "添加描述、建议操作和带项目符号的分类步骤。使用提供的 MITRE ATT&CK 数据以从 MITRE 添加更多上下文和建议,以及指向 MITRE 网站上的相关页面的超链接。确保包括上下文中的用户和主机风险分数数据。您的响应应包含指向 Elastic Security 特定功能的步骤包括终端响应操作、Elastic 代理 OSQuery 管理器集成(带示例 osquery 查询)、时间线和实体分析,以及所有相关 Elastic Security 文档的链接。",
"xpack.securitySolution.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries": "评估来自上述上下文的事件,并以用于我的 Elastic Security 案例的 Markdown 语法对您的输出进行全面格式化。",
"xpack.securitySolution.assistant.conversationMigrationStatus.title": "已成功保持本地存储对话。",
"xpack.securitySolution.assistant.quickPrompts.alertSummarizationPrompt": "作为安全运营和事件响应领域的专家,提供附加告警的细目并简要说明它对我所在组织可能的影响。",
"xpack.securitySolution.assistant.quickPrompts.alertSummarizationTitle": "告警汇总",
"xpack.securitySolution.assistant.quickPrompts.AutomationPrompt": "我应使用哪个启用 Fleet 的 Elastic 代理集成从以下项中收集日志和事件:",
@ -37027,7 +37032,6 @@
"xpack.securitySolution.detectionEngine.ruleManagement.createRule.threatMappingField.requiredError": "至少需要一个指标匹配。",
"xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesConversationId": "检测规则",
"xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesCreateEditFormConversationId": "检测规则创建了表单",
"xpack.securitySolution.detectionEngine.ruleManagement.explainThenSummarizeRuleDetails": "请解释上面的选定规则。对于每个规则,重点说明它们为什么相关、在 Elastic 的检测规则存储库中发布的查询并提供深入解释,以及它们通常对组织的影响(如果检测到)。",
"xpack.securitySolution.detectionEngine.ruleManagement.fields.from.helpText": "日期数学表达式如“now”、“now-3d”、“now+2m”。",
"xpack.securitySolution.detectionEngine.ruleManagement.fields.from.label": "自",
"xpack.securitySolution.detectionEngine.ruleManagement.fields.interval.helpText": "规则定期运行并检测指定时间范围内的告警。",

View file

@ -9,21 +9,225 @@ import React from 'react';
import { screen, render } from '@testing-library/react';
import { ChatAction } from '.';
import { TestDataQualityProviders } from '../../mock/test_providers/test_providers';
import { NewChat, useFindPrompts } from '@kbn/elastic-assistant';
import {
TestDataQualityProviders,
TestExternalProviders,
} from '../../mock/test_providers/test_providers';
DATA_QUALITY_PROMPT_CONTEXT_PILL,
DATA_QUALITY_SUGGESTED_USER_PROMPT,
} from '../../translations';
import { getFormattedCheckTime } from '../../data_quality_details/indices_details/pattern/index_check_flyout/utils/get_formatted_check_time';
jest.mock('@kbn/elastic-assistant', () => ({
NewChat: jest.fn(({ children }) => (
<button type="button" data-test-subj="newChatLink">
{children}
</button>
)),
useFindPrompts: jest.fn().mockReturnValue({
data: { prompts: [] },
}),
}));
const useFindPromptsMock = useFindPrompts as unknown as jest.Mock<
Pick<ReturnType<typeof useFindPrompts>, 'data'>
>;
const NewChatMock = NewChat as jest.MockedFunction<typeof NewChat>;
describe('ChatAction', () => {
it('should render new chat link', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should render new chat link', async () => {
render(
<TestExternalProviders>
<TestDataQualityProviders>
<ChatAction markdownComment="test markdown" indexName="test-index" />
</TestDataQualityProviders>
</TestExternalProviders>
<TestDataQualityProviders>
<ChatAction markdownComment="test markdown" indexName="test-index" />
</TestDataQualityProviders>
);
expect(screen.getByTestId('newChatLink')).toHaveTextContent('Ask Assistant');
});
it('should pass correct props to NewChat with minimal required props', () => {
const markdownComment = 'test markdown comment';
const indexName = 'test-index-name';
render(
<TestDataQualityProviders>
<ChatAction markdownComment={markdownComment} indexName={indexName} />
</TestDataQualityProviders>
);
expect(NewChatMock).toHaveBeenCalledWith(
expect.objectContaining({
asLink: true,
category: 'data-quality-dashboard',
conversationTitle: '--',
description: expect.any(String),
getPromptContext: expect.any(Function),
suggestedUserPrompt: expect.any(String),
tooltip: expect.any(String),
isAssistantEnabled: true,
iconType: null,
}),
{}
);
});
it('should pass index name in NewChat description prop', () => {
const markdownComment = 'test markdown';
const indexName = 'specific-test-index';
render(
<TestDataQualityProviders>
<ChatAction markdownComment={markdownComment} indexName={indexName} />
</TestDataQualityProviders>
);
const callArgs = NewChatMock.mock.calls[0][0];
expect(callArgs.description).toBe(DATA_QUALITY_PROMPT_CONTEXT_PILL(indexName));
});
describe('when checkedAt timestamp is provided', () => {
it('should pass correct conversation title with formatted timestamp', () => {
const markdownComment = 'test markdown';
const indexName = 'test-index';
const checkedAt = 1640995200000;
render(
<TestDataQualityProviders>
<ChatAction
markdownComment={markdownComment}
indexName={indexName}
checkedAt={checkedAt}
/>
</TestDataQualityProviders>
);
const expectedTitle = `${indexName} - ${getFormattedCheckTime(checkedAt)}`;
expect(NewChatMock).toHaveBeenCalledWith(
expect.objectContaining({
conversationTitle: expectedTitle,
}),
{}
);
});
});
describe('when checkedAt timestamp is not provided', () => {
it('should replace conversation title with empty stat "--" fallback', () => {
const markdownComment = 'test markdown';
const indexName = 'test-index';
render(
<TestDataQualityProviders>
<ChatAction markdownComment={markdownComment} indexName={indexName} />
</TestDataQualityProviders>
);
expect(NewChatMock).toHaveBeenCalledWith(
expect.objectContaining({
conversationTitle: '--',
}),
{}
);
});
});
describe('when dataQualityAnalysis prompt is available', () => {
it('should use it', () => {
const dqdPrompt = 'Custom data quality analysis prompt';
const markdownComment = 'test markdown';
const indexName = 'test-index';
useFindPromptsMock.mockReturnValue({
data: {
prompts: [
{ promptId: 'dataQualityAnalysis', prompt: dqdPrompt },
{ promptId: 'other', prompt: 'Other prompt' },
],
},
});
render(
<TestDataQualityProviders>
<ChatAction markdownComment={markdownComment} indexName={indexName} />
</TestDataQualityProviders>
);
expect(NewChatMock).toHaveBeenCalledWith(
expect.objectContaining({
suggestedUserPrompt: dqdPrompt,
}),
{}
);
});
});
describe('when dataQualityAnalysis prompt is not available', () => {
it('should use fallback prompt', () => {
const markdownComment = 'test markdown';
const indexName = 'test-index';
(useFindPrompts as jest.Mock).mockReturnValue({
data: {
prompts: [{ promptId: 'other', prompt: 'Other prompt' }],
},
});
render(
<TestDataQualityProviders>
<ChatAction markdownComment={markdownComment} indexName={indexName} />
</TestDataQualityProviders>
);
expect(NewChatMock).toHaveBeenCalledWith(
expect.objectContaining({
suggestedUserPrompt: DATA_QUALITY_SUGGESTED_USER_PROMPT,
}),
{}
);
});
});
it('should call useFindPrompts hook with correct context and params', () => {
const markdownComment = 'test markdown';
const indexName = 'test-index';
render(
<TestDataQualityProviders>
<ChatAction markdownComment={markdownComment} indexName={indexName} />
</TestDataQualityProviders>
);
expect(useFindPromptsMock).toHaveBeenCalledWith({
context: {
isAssistantEnabled: true,
httpFetch: expect.any(Function),
toasts: expect.any(Object),
},
params: {
prompt_group_id: 'aiAssistant',
prompt_ids: ['dataQualityAnalysis'],
},
});
});
it('should provide NewChat getPromptContext function that returns markdownComment', async () => {
const markdownComment = 'test markdown comment for context';
const indexName = 'test-index';
render(
<TestDataQualityProviders>
<ChatAction markdownComment={markdownComment} indexName={indexName} />
</TestDataQualityProviders>
);
const callArgs = NewChatMock.mock.calls[0][0];
const getPromptContext = callArgs.getPromptContext;
await expect(getPromptContext()).resolves.toBe(markdownComment);
});
});

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import React, { FC, useCallback, useMemo } from 'react';
import { NewChat } from '@kbn/elastic-assistant';
import type { FC } from 'react';
import React, { useCallback, useMemo } from 'react';
import { NewChat, useFindPrompts } from '@kbn/elastic-assistant';
import { css } from '@emotion/react';
import { useEuiTheme } from '@elastic/eui';
@ -40,12 +41,34 @@ interface Props {
const ChatActionComponent: FC<Props> = ({ indexName, markdownComment, checkedAt }) => {
const chatTitle = useMemo(() => {
return `${indexName} - ${getFormattedCheckTime(checkedAt)}`;
const checkedAtFormatted = getFormattedCheckTime(checkedAt);
return checkedAt && indexName ? `${indexName} - ${checkedAtFormatted}` : checkedAtFormatted;
}, [checkedAt, indexName]);
const styles = useStyles();
const { isAssistantEnabled } = useDataQualityContext();
const { isAssistantEnabled, httpFetch, toasts } = useDataQualityContext();
const {
data: { prompts },
} = useFindPrompts({
context: {
isAssistantEnabled,
httpFetch,
toasts,
},
params: {
prompt_group_id: 'aiAssistant',
prompt_ids: ['dataQualityAnalysis'],
},
});
const getPromptContext = useCallback(async () => markdownComment, [markdownComment]);
const suggestedUserPrompt = useMemo(
() =>
prompts.find(({ promptId }) => promptId === 'dataQualityAnalysis')?.prompt ??
DATA_QUALITY_SUGGESTED_USER_PROMPT,
[prompts]
);
return (
<NewChat
asLink={true}
@ -53,12 +76,12 @@ const ChatActionComponent: FC<Props> = ({ indexName, markdownComment, checkedAt
conversationTitle={chatTitle ?? DATA_QUALITY_DASHBOARD_CONVERSATION_ID}
description={DATA_QUALITY_PROMPT_CONTEXT_PILL(indexName)}
getPromptContext={getPromptContext}
suggestedUserPrompt={DATA_QUALITY_SUGGESTED_USER_PROMPT}
suggestedUserPrompt={suggestedUserPrompt}
tooltip={DATA_QUALITY_PROMPT_CONTEXT_PILL_TOOLTIP}
isAssistantEnabled={isAssistantEnabled}
iconType={null}
>
<span css={styles.linkText}>
<span css={styles.linkText} data-testid="newChatLink">
<AssistantIcon />
{ASK_ASSISTANT}
</span>

View file

@ -10,6 +10,7 @@ import moment from 'moment-timezone';
moment.tz.setDefault('UTC');
import { getFormattedCheckTime } from './get_formatted_check_time';
import { EMPTY_STAT } from '../../../../../constants';
describe('getFormattedCheckTime', () => {
it('returns formatted check time', () => {
@ -23,4 +24,11 @@ describe('getFormattedCheckTime', () => {
expect(formattedCheckTime).toBe('--');
});
});
describe('when check time is not provided', () => {
it(`returns ${EMPTY_STAT} string`, () => {
const formattedCheckTime = getFormattedCheckTime();
expect(formattedCheckTime).toBe(EMPTY_STAT);
});
});
});

View file

@ -10,4 +10,6 @@ import moment from 'moment';
import { EMPTY_STAT } from '../../../../../constants';
export const getFormattedCheckTime = (checkedAt?: number) =>
moment(checkedAt).isValid() ? moment(checkedAt).format('MMM DD, YYYY @ HH:mm:ss') : EMPTY_STAT;
checkedAt && moment(checkedAt).isValid()
? moment(checkedAt).format('MMM DD, YYYY @ HH:mm:ss')
: EMPTY_STAT;

View file

@ -78,10 +78,10 @@ export const DATA_QUALITY_SUBTITLE: string = i18n.translate(
);
export const DATA_QUALITY_SUGGESTED_USER_PROMPT = i18n.translate(
'securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt',
'securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPromptV2',
{
defaultMessage:
'Explain the results above, and describe some options to fix incompatibilities.',
'Explain the ECS incompatibility results above, and describe some options to fix incompatibilities. In your explanation, include information about remapping fields, reindexing data, and modifying data ingestion pipelines. Also, describe how ES|QL can be used to identify and correct incompatible data, including examples of using RENAME, EVAL, DISSECT, GROK, and CASE functions.',
}
);

View file

@ -402,6 +402,17 @@ describe('get_prompt', () => {
expect(result).toBe('Hello world this is a system prompt for bedrock claude-3-5-sonnet');
});
it('finds the default prompt if no provider/model are indicated and no connector details are provided', async () => {
const result = await getPrompt({
savedObjectsClient,
localPrompts,
promptId: promptDictionary.systemPrompt,
promptGroupId: promptGroupId.aiAssistant,
});
expect(result).toEqual('Hello world this is a system prompt no model, no provider');
});
});
describe('getPromptsByGroupId', () => {
@ -533,5 +544,21 @@ describe('get_prompt', () => {
})
).rejects.toThrow('Prompt not found for promptId: fake-id and promptGroupId: aiAssistant');
});
it('finds the default prompt if no provider/model are indicated and no connector details are provided', async () => {
const result = await getPromptsByGroupId({
savedObjectsClient,
localPrompts,
promptIds: [promptDictionary.systemPrompt],
promptGroupId: promptGroupId.aiAssistant,
});
expect(result).toEqual([
{
promptId: promptDictionary.systemPrompt,
prompt: 'Hello world this is a system prompt no model, no provider',
},
]);
});
});
});

View file

@ -16,7 +16,7 @@ import { promptSavedObjectType } from './saved_object_mappings';
/**
* Get prompts by feature (promptGroupId)
* provide either model + provider or connector to avoid additional calls to get connector
* @param actionsClient - actions client
* @param actionsClient - actions client (look up connector if connector is not provided)
* @param connector - connector, provide if available. No need to provide model and provider in this case
* @param connectorId - connector id
* @param localPrompts - local prompts object
@ -138,15 +138,20 @@ export const resolveProviderAndModel = async ({
}: {
providedProvider?: string;
providedModel?: string;
connectorId: string;
actionsClient: PublicMethodsOf<ActionsClient>;
connectorId?: string;
actionsClient?: PublicMethodsOf<ActionsClient>;
providedConnector?: Connector;
}): Promise<{ provider?: string; model?: string }> => {
let model = providedModel;
let provider = providedProvider;
if (!provider || !model || provider === 'inference') {
const connector = providedConnector ?? (await actionsClient.get({ id: connectorId }));
let connector = providedConnector;
if (!connector && connectorId != null && actionsClient) {
connector = await actionsClient.get({ id: connectorId });
}
if (!connector) {
return {};
}
if (provider === 'inference' && connector.config) {
provider = connector.config.provider || provider;
model = connector.config.providerConfig?.model_id || model;

View file

@ -24,9 +24,9 @@ export interface Prompt {
export type PromptArray = Array<{ promptId: string; prompt: string }>;
export interface GetPromptArgs {
actionsClient: PublicMethodsOf<ActionsClient>;
actionsClient?: PublicMethodsOf<ActionsClient>;
connector?: Connector;
connectorId: string;
connectorId?: string;
localPrompts: Prompt[];
model?: string;
promptId: string;

View file

@ -49,6 +49,7 @@ import {
ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND,
PerformKnowledgeBaseEntryBulkActionRequestBody,
PostEvaluateRequestBodyInput,
ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
} from '@kbn/elastic-assistant-common';
import {
getAppendConversationMessagesSchemaMock,
@ -167,6 +168,13 @@ export const getCurrentUserPromptsRequest = () =>
path: ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND,
});
export const getCurrentUserSecurityAIPromptsRequest = () =>
requestMock.create({
method: 'get',
path: ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
query: { prompt_group_id: 'aiAssistant', prompt_ids: ['systemPrompt'] },
});
export const getCurrentUserAlertSummaryRequest = () =>
requestMock.create({
method: 'get',

View file

@ -26,6 +26,9 @@ import {
DEFEND_INSIGHTS,
ALERT_SUMMARY_500,
ALERT_SUMMARY_SYSTEM_PROMPT,
RULE_ANALYSIS,
DATA_QUALITY_ANALYSIS,
ALERT_EVALUATION,
} from './prompts';
export const promptGroupId = {
@ -38,6 +41,9 @@ export const promptGroupId = {
};
export const promptDictionary = {
alertEvaluation: `alertEvaluation`,
dataQualityAnalysis: 'dataQualityAnalysis',
ruleAnalysis: 'ruleAnalysis',
alertSummary: `alertSummary`,
alertSummarySystemPrompt: `alertSummarySystemPrompt`,
systemPrompt: `systemPrompt`,
@ -61,6 +67,7 @@ export const promptDictionary = {
defendInsightsIncompatibleAntivirusEventsEndpointId:
'defendInsights-incompatibleAntivirusEventsEndpointId',
defendInsightsIncompatibleAntivirusEventsValue: 'defendInsights-incompatibleAntivirusEventsValue',
// context prompts
};
export const localPrompts: Prompt[] = [
@ -259,4 +266,25 @@ export const localPrompts: Prompt[] = [
default: ALERT_SUMMARY_SYSTEM_PROMPT,
},
},
{
promptId: promptDictionary.alertEvaluation,
promptGroupId: promptGroupId.aiAssistant,
prompt: {
default: ALERT_EVALUATION,
},
},
{
promptId: promptDictionary.dataQualityAnalysis,
promptGroupId: promptGroupId.aiAssistant,
prompt: {
default: DATA_QUALITY_ANALYSIS,
},
},
{
promptId: promptDictionary.ruleAnalysis,
promptGroupId: promptGroupId.aiAssistant,
prompt: {
default: RULE_ANALYSIS,
},
},
];

View file

@ -218,3 +218,40 @@ export const ALERT_SUMMARY_SYSTEM_PROMPT =
'\n' +
'The response should look like this:\n' +
'{{"summary":"Markdown-formatted summary text.","recommendedActions":"Markdown-formatted action list starting with a ### header."}}';
export const RULE_ANALYSIS =
'Please provide a comprehensive analysis of each selected Elastic Security detection rule. For each rule, include:\n' +
'- The rule name and a brief summary of its purpose.\n' +
'- The full detection query as published in Elastics official detection rules repository.\n' +
'- An in-depth explanation of how the query works, including key fields, logic, and detection techniques.\n' +
'- The relevance of the rule to modern threats or attack techniques (e.g., MITRE ATT&CK mapping).\n' +
'- Typical implications and recommended response actions for an organization if this rule triggers.\n' +
'- Any notable false positive considerations or tuning recommendations.\n' +
'Format your response using markdown with clear headers for each rule, code blocks for queries, and concise bullet points for explanations.';
export const DATA_QUALITY_ANALYSIS =
'Explain the ECS incompatibility results above, and describe some options to fix incompatibilities. In your explanation, include information about remapping fields, reindexing data, and modifying data ingestion pipelines. Also, describe how ES|QL can be used to identify and correct incompatible data, including examples of using RENAME, EVAL, DISSECT, GROK, and CASE functions.';
export const ALERT_EVALUATION = `Evaluate the security event described above and provide a structured, markdown-formatted summary suitable for inclusion in an Elastic Security case. Ensure you're using all tools available to you. Your response must include:
1. Event Description
- Summarize the event, including user and host risk scores from the provided context.
- Reference relevant MITRE ATT&CK techniques, with hyperlinks to the official MITRE pages.
2. Triage Steps
- List clear, bulleted triage steps tailored to Elastic Security workflows (e.g., alert investigation, timeline creation, entity analytics review).
- Highlight any relevant detection rules or anomaly findings.
3. Recommended Actions
- Provide prioritized response actions, including:
- Elastic Defend endpoint response actions (e.g., isolate host, kill process, retrieve/delete file), with links to Elastic documentation.
- Example ES|QL queries for further investigation, formatted as code blocks.
- Example OSQuery Manager queries for further investigation, formatted as code blocks.
- Guidance on using Timelines and Entity Analytics for deeper context, with documentation links.
4. MITRE ATT&CK Context
- Summarize the mapped MITRE ATT&CK techniques and provide actionable recommendations based on MITRE guidance, with hyperlinks.
5. Documentation Links
- Include direct links to all referenced Elastic Security documentation and MITRE ATT&CK pages.
Formatting Requirements:
- Use markdown headers, tables, and code blocks for clarity.
- Organize the response into visually distinct sections.
- Use concise, actionable language.
- Include relevant emojis in section headers for visual clarity (e.g., 📝, 🛡, 🔍, 📚).
`;

View file

@ -7,6 +7,7 @@
import type { Logger } from '@kbn/core/server';
import { findSecurityAIPromptsRoute } from './security_ai_prompts/find_prompts';
import { findAlertSummaryRoute } from './alert_summary/find_route';
import { cancelAttackDiscoveryRoute } from './attack_discovery/post/cancel/cancel_attack_discovery';
import { findAttackDiscoveriesRoute } from './attack_discovery/get/find_attack_discoveries';
@ -108,6 +109,9 @@ export const registerRoutes = (
bulkPromptsRoute(router, logger);
findPromptsRoute(router, logger);
// Security AI Prompts
findSecurityAIPromptsRoute(router, logger);
// Anonymization Fields
bulkActionAnonymizationFieldsRoute(router, logger);
findAnonymizationFieldsRoute(router, logger);

View file

@ -0,0 +1,106 @@
/*
* 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 { getCurrentUserSecurityAIPromptsRequest, requestMock } from '../../__mocks__/request';
import { ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND } from '@kbn/elastic-assistant-common';
import { serverMock } from '../../__mocks__/server';
import { requestContextMock } from '../../__mocks__/request_context';
import { findSecurityAIPromptsRoute } from './find_prompts';
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
import type { AuthenticatedUser } from '@kbn/core-security-common';
import { getPromptsByGroupId } from '../../lib/prompt';
import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock';
jest.mock('../../lib/prompt');
const mockResponse = [{ promptId: 'systemPrompt', prompt: 'This is a prompt' }];
describe('Find security AI prompts route', () => {
let server: ReturnType<typeof serverMock.create>;
let { context } = requestContextMock.createTools();
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
beforeEach(async () => {
server = serverMock.create();
({ context } = requestContextMock.createTools());
const mockUser1 = {
username: 'my_username',
authentication_realm: {
type: 'my_realm_type',
name: 'my_realm_name',
},
} as AuthenticatedUser;
(getPromptsByGroupId as jest.Mock).mockResolvedValue(Promise.resolve(mockResponse));
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce({
username: 'my_username',
authentication_realm: {
type: 'my_realm_type',
name: 'my_realm_name',
},
} as AuthenticatedUser);
(context.elasticAssistant.actions.getActionsClientWithRequest as jest.Mock) = jest
.fn()
.mockReturnValueOnce(actionsClientMock.create());
logger = loggingSystemMock.createLogger();
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
findSecurityAIPromptsRoute(server.router, logger);
});
describe('status codes', () => {
it('returns 200', async () => {
const response = await server.inject(
getCurrentUserSecurityAIPromptsRequest(),
requestContextMock.convertContext(context)
);
expect(response.status).toEqual(200);
expect(response.body).toEqual({ prompts: mockResponse });
});
it('catches error if search throws error', async () => {
(getPromptsByGroupId as jest.Mock).mockRejectedValueOnce(new Error('Test error'));
const response = await server.inject(
getCurrentUserSecurityAIPromptsRequest(),
requestContextMock.convertContext(context)
);
expect(response.status).toEqual(500);
expect(response.body).toEqual({
message: 'Test error',
status_code: 500,
});
});
});
describe('request validation', () => {
it('allows optional query params', async () => {
const request = requestMock.create({
method: 'get',
path: ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
query: {
prompt_group_id: 'aiAssistant',
prompt_ids: ['systemPrompt'],
connector_id: '123',
},
});
const result = server.validate(request);
expect(result.ok).toHaveBeenCalled();
});
it('ignores unknown query params', async () => {
const request = requestMock.create({
method: 'get',
path: ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
query: {
prompt_group_id: 'aiAssistant',
prompt_ids: ['systemPrompt'],
invalid_value: 'test 1',
},
});
const result = server.validate(request);
expect(result.ok).toHaveBeenCalled();
});
});
});

View file

@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { IKibanaResponse, Logger } from '@kbn/core/server';
import { transformError } from '@kbn/securitysolution-es-utils';
import {
API_VERSIONS,
ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
FindSecurityAIPromptsRequestQuery,
FindSecurityAIPromptsResponse,
} from '@kbn/elastic-assistant-common';
import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common';
import { getPromptsByGroupId } from '../../lib/prompt';
import { ElasticAssistantPluginRouter } from '../../types';
import { buildResponse } from '../utils';
import { performChecks } from '../helpers';
export const findSecurityAIPromptsRoute = (router: ElasticAssistantPluginRouter, logger: Logger) =>
router.versioned
.get({
access: 'public',
path: ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND,
security: {
authz: {
requiredPrivileges: ['elasticAssistant'],
},
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {
query: buildRouteValidationWithZod(FindSecurityAIPromptsRequestQuery),
},
response: {
200: {
body: { custom: buildRouteValidationWithZod(FindSecurityAIPromptsResponse) },
},
},
},
},
async (
context,
request,
response
): Promise<IKibanaResponse<FindSecurityAIPromptsResponse>> => {
const assistantResponse = buildResponse(response);
try {
const { query } = request;
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
// Perform license and authenticated user checks
const checkResponse = await performChecks({
context: ctx,
request,
response,
});
if (!checkResponse.isSuccess) {
return checkResponse.response;
}
const actions = ctx.elasticAssistant.actions;
const actionsClient = await actions.getActionsClientWithRequest(request);
const savedObjectsClient = ctx.elasticAssistant.savedObjectsClient;
const prompts = await getPromptsByGroupId({
actionsClient,
connectorId: query.connector_id,
promptGroupId: query.prompt_group_id,
promptIds: query.prompt_ids,
savedObjectsClient,
});
return response.ok({
body: {
prompts,
},
});
} catch (err) {
const error = transformError(err);
return assistantResponse.error({
body: error.message,
statusCode: error.statusCode,
});
}
}
);

View file

@ -10,13 +10,11 @@ import * as i18nDataQuality from '@kbn/ecs-data-quality-dashboard';
import * as i18n from './translations';
import * as i18nDetections from '../../../detection_engine/common/translations';
import * as i18nEventDetails from '../../../common/components/event_details/translations';
import * as i18nUserPrompts from '../prompts/user/translations';
export const PROMPT_CONTEXT_ALERT_CATEGORY = 'alert';
export const PROMPT_CONTEXT_EVENT_CATEGORY = 'event';
export const PROMPT_CONTEXT_DETECTION_RULES_CATEGORY = 'detection-rules';
export const DATA_QUALITY_DASHBOARD_CATEGORY = 'data-quality-dashboard';
export const KNOWLEDGE_BASE_CATEGORY = 'knowledge-base';
/**
* Global list of PromptContexts intended to be used throughout Security Solution.
@ -24,14 +22,15 @@ export const KNOWLEDGE_BASE_CATEGORY = 'knowledge-base';
* a unique set of categories to reference since the PromptContexts available on
* useAssistantContext are dynamic (not globally registered).
*/
export const PROMPT_CONTEXTS: Record<PromptContext['category'], PromptContextTemplate> = {
export const getPromptContexts = (
prompts: Record<PromptContext['category'], string>
): Record<PromptContext['category'], PromptContextTemplate> => ({
/**
* Alert summary view context, made available on the alert details flyout
*/
[PROMPT_CONTEXT_ALERT_CATEGORY]: {
category: PROMPT_CONTEXT_ALERT_CATEGORY,
suggestedUserPrompt:
i18nUserPrompts.EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N,
suggestedUserPrompt: prompts[PROMPT_CONTEXT_ALERT_CATEGORY],
description: i18nEventDetails.ALERT_SUMMARY_CONTEXT_DESCRIPTION(i18n.VIEW),
tooltip: i18nEventDetails.ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP,
},
@ -40,8 +39,7 @@ export const PROMPT_CONTEXTS: Record<PromptContext['category'], PromptContextTem
*/
[PROMPT_CONTEXT_EVENT_CATEGORY]: {
category: PROMPT_CONTEXT_EVENT_CATEGORY,
suggestedUserPrompt:
i18nUserPrompts.EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N,
suggestedUserPrompt: prompts[PROMPT_CONTEXT_EVENT_CATEGORY],
description: i18nEventDetails.EVENT_SUMMARY_CONTEXT_DESCRIPTION(i18n.VIEW),
tooltip: i18nEventDetails.EVENT_SUMMARY_VIEW_CONTEXT_TOOLTIP,
},
@ -50,7 +48,7 @@ export const PROMPT_CONTEXTS: Record<PromptContext['category'], PromptContextTem
*/
[DATA_QUALITY_DASHBOARD_CATEGORY]: {
category: DATA_QUALITY_DASHBOARD_CATEGORY,
suggestedUserPrompt: i18nDataQuality.DATA_QUALITY_SUGGESTED_USER_PROMPT,
suggestedUserPrompt: prompts[DATA_QUALITY_DASHBOARD_CATEGORY],
description: i18nDataQuality.DATA_QUALITY_PROMPT_CONTEXT_PILL(i18n.INDEX),
tooltip: i18nDataQuality.DATA_QUALITY_PROMPT_CONTEXT_PILL_TOOLTIP,
},
@ -59,8 +57,8 @@ export const PROMPT_CONTEXTS: Record<PromptContext['category'], PromptContextTem
*/
[PROMPT_CONTEXT_DETECTION_RULES_CATEGORY]: {
category: PROMPT_CONTEXT_DETECTION_RULES_CATEGORY,
suggestedUserPrompt: i18nDetections.EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS,
suggestedUserPrompt: prompts[PROMPT_CONTEXT_DETECTION_RULES_CATEGORY],
description: i18nDetections.RULE_MANAGEMENT_CONTEXT_DESCRIPTION,
tooltip: i18nDetections.RULE_MANAGEMENT_CONTEXT_TOOLTIP,
},
};
});

View file

@ -0,0 +1,108 @@
/*
* 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 { renderHook } from '@testing-library/react';
import type { UseFindPromptContextsParams } from './use_find_prompt_contexts';
import { useFindPromptContexts } from './use_find_prompt_contexts';
import { useFindPrompts } from '@kbn/elastic-assistant';
import { DATA_QUALITY_SUGGESTED_USER_PROMPT } from '@kbn/ecs-data-quality-dashboard';
import { EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS } from '../../../detection_engine/common/translations';
import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE } from '../prompts/user/translations';
const mockPrompts = [
{
promptId: 'alertEvaluation',
prompt: 'ALERT EVALUATION',
},
{
promptId: 'dataQualityAnalysis',
prompt: 'DATA QUALITY ANALYSIS',
},
{
promptId: 'ruleAnalysis',
prompt: 'RULE ANALYSIS',
},
];
jest.mock('@kbn/elastic-assistant');
describe('useFindPromptContexts', () => {
beforeEach(() => {
jest.clearAllMocks();
(useFindPrompts as jest.Mock).mockReturnValue({ data: { prompts: mockPrompts } });
});
it('calls getPromptContexts with the correct prompts mapped by category', () => {
const params = {} as unknown as UseFindPromptContextsParams;
renderHook(() => useFindPromptContexts(params));
const mockReturn = {
alert: {
category: 'alert',
description: 'Alert (from view)',
suggestedUserPrompt: 'ALERT EVALUATION',
tooltip: 'Add this alert as context',
},
'data-quality-dashboard': {
category: 'data-quality-dashboard',
description: 'Data Quality (index)',
suggestedUserPrompt: 'DATA QUALITY ANALYSIS',
tooltip: 'Add this Data Quality report as context',
},
'detection-rules': {
category: 'detection-rules',
description: 'Selected Detection Rules',
suggestedUserPrompt: 'RULE ANALYSIS',
tooltip: 'Add this alert as context',
},
event: {
category: 'event',
description: 'Event (from view)',
suggestedUserPrompt: 'ALERT EVALUATION',
tooltip: 'Add this event as context',
},
};
const { result } = renderHook(() => useFindPromptContexts(params));
expect(result.current).toStrictEqual(mockReturn);
});
it('uses correct fallback values when the API does not contain the expected results', () => {
(useFindPrompts as jest.Mock).mockReturnValue({ data: { prompts: [] } });
const params = {} as unknown as UseFindPromptContextsParams;
renderHook(() => useFindPromptContexts(params));
const mockReturn = {
alert: {
category: 'alert',
description: 'Alert (from view)',
suggestedUserPrompt: EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE,
tooltip: 'Add this alert as context',
},
'data-quality-dashboard': {
category: 'data-quality-dashboard',
description: 'Data Quality (index)',
suggestedUserPrompt: DATA_QUALITY_SUGGESTED_USER_PROMPT,
tooltip: 'Add this Data Quality report as context',
},
'detection-rules': {
category: 'detection-rules',
description: 'Selected Detection Rules',
suggestedUserPrompt: EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS,
tooltip: 'Add this alert as context',
},
event: {
category: 'event',
description: 'Event (from view)',
suggestedUserPrompt: EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE,
tooltip: 'Add this event as context',
},
};
const { result } = renderHook(() => useFindPromptContexts(params));
expect(result.current).toStrictEqual(mockReturn);
});
});

View file

@ -0,0 +1,52 @@
/*
* 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 { useFindPrompts } from '@kbn/elastic-assistant';
import type { HttpHandler } from '@kbn/core-http-browser';
import type { IToasts } from '@kbn/core-notifications-browser';
import type { FindSecurityAIPromptsRequestQuery } from '@kbn/elastic-assistant-common';
import { DATA_QUALITY_SUGGESTED_USER_PROMPT } from '@kbn/ecs-data-quality-dashboard';
import { EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS } from '../../../detection_engine/common/translations';
import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE } from '../prompts/user/translations';
import {
DATA_QUALITY_DASHBOARD_CATEGORY,
getPromptContexts,
PROMPT_CONTEXT_ALERT_CATEGORY,
PROMPT_CONTEXT_DETECTION_RULES_CATEGORY,
PROMPT_CONTEXT_EVENT_CATEGORY,
} from '.';
export interface UseFindPromptContextsParams {
context: {
isAssistantEnabled: boolean;
httpFetch: HttpHandler;
toasts: IToasts;
};
signal?: AbortSignal | undefined;
params: FindSecurityAIPromptsRequestQuery;
}
export const useFindPromptContexts = (payload: UseFindPromptContextsParams) => {
const {
data: { prompts },
} = useFindPrompts(payload);
const PROMPT_CONTEXTS = getPromptContexts({
[PROMPT_CONTEXT_ALERT_CATEGORY]:
prompts.find(({ promptId }) => promptId === 'alertEvaluation')?.prompt ??
EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE,
[PROMPT_CONTEXT_EVENT_CATEGORY]:
prompts.find(({ promptId }) => promptId === 'alertEvaluation')?.prompt ??
EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE,
[DATA_QUALITY_DASHBOARD_CATEGORY]:
prompts.find(({ promptId }) => promptId === 'dataQualityAnalysis')?.prompt ??
DATA_QUALITY_SUGGESTED_USER_PROMPT,
[PROMPT_CONTEXT_DETECTION_RULES_CATEGORY]:
prompts.find(({ promptId }) => promptId === 'ruleAnalysis')?.prompt ??
EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS,
});
return PROMPT_CONTEXTS;
};

View file

@ -7,20 +7,31 @@
import { i18n } from '@kbn/i18n';
export const THEN_SUMMARIZE_SUGGESTED_KQL_AND_EQL_QUERIES = i18n.translate(
'xpack.securitySolution.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries',
export const EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE = i18n.translate(
'xpack.securitySolution.assistant.content.prompts.investigationGuide',
{
defaultMessage:
'Evaluate the event from the context above and format your output neatly in markdown syntax for my Elastic Security case.',
defaultMessage: `Evaluate the security event described above and provide a structured, markdown-formatted summary suitable for inclusion in an Elastic Security case. Ensure you're using all tools available to you. Your response must include:
1. Event Description
- Summarize the event, including user and host risk scores from the provided context.
- Reference relevant MITRE ATT&CK techniques, with hyperlinks to the official MITRE pages.
2. Triage Steps
- List clear, bulleted triage steps tailored to Elastic Security workflows (e.g., alert investigation, timeline creation, entity analytics review).
- Highlight any relevant detection rules or anomaly findings.
3. Recommended Actions
- Provide prioritized response actions, including:
- Elastic Defend endpoint response actions (e.g., isolate host, kill process, retrieve/delete file), with links to Elastic documentation.
- Example ES|QL queries for further investigation, formatted as code blocks.
- Example OSQuery Manager queries for further investigation, formatted as code blocks.
- Guidance on using Timelines and Entity Analytics for deeper context, with documentation links.
4. MITRE ATT&CK Context
- Summarize the mapped MITRE ATT&CK techniques and provide actionable recommendations based on MITRE guidance, with hyperlinks.
5. Documentation Links
- Include direct links to all referenced Elastic Security documentation and MITRE ATT&CK pages.
Formatting Requirements:
- Use markdown headers, tables, and code blocks for clarity.
- Organize the response into visually distinct sections.
- Use concise, actionable language.
- Include relevant emojis in section headers for visual clarity (e.g., 📝, 🛡, 🔍, 📚).
`,
}
);
export const FINALLY_SUGGEST_INVESTIGATION_GUIDE_AND_FORMAT_AS_MARKDOWN = i18n.translate(
'xpack.securitySolution.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown',
{
defaultMessage: `Add your description, recommended actions and bulleted triage steps. Use the MITRE ATT&CK data provided to add more context and recommendations from MITRE, and hyperlink to the relevant pages on MITRE\'s website. Be sure to include the user and host risk score data from the context. Your response should include steps that point to Elastic Security specific features, including endpoint response actions, the Elastic Agent OSQuery manager integration (with example osquery queries), timelines and entity analytics and link to all the relevant Elastic Security documentation.`,
}
);
export const EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N = `${THEN_SUMMARIZE_SUGGESTED_KQL_AND_EQL_QUERIES}
${FINALLY_SUGGEST_INVESTIGATION_GUIDE_AND_FORMAT_AS_MARKDOWN}`;

View file

@ -13,7 +13,7 @@ import {
bulkUpdatePrompts,
} from '@kbn/elastic-assistant';
import { once } from 'lodash/fp';
import { once, isEmpty } from 'lodash/fp';
import type { HttpSetup } from '@kbn/core-http-browser';
import useObservable from 'react-use/lib/useObservable';
import { useKibana } from '../common/lib/kibana';
@ -21,6 +21,7 @@ import { useKibana } from '../common/lib/kibana';
import { BASE_SECURITY_QUICK_PROMPTS } from './content/quick_prompts';
import { useAssistantAvailability } from './use_assistant_availability';
import { licenseService } from '../common/hooks/use_license';
import { useFindPromptContexts } from './content/prompt_contexts/use_find_prompt_contexts';
import { CommentActionsPortal } from './comment_actions/comment_actions_portal';
import { AugmentMessageCodeBlocksPortal } from './use_augment_message_code_blocks/augment_message_code_blocks_portal';
import { useElasticAssistantSharedStateSignalIndex } from './use_elastic_assistant_shared_state_signal_index/use_elastic_assistant_shared_state_signal_index';
@ -86,6 +87,30 @@ export const AssistantProvider: FC<PropsWithChildren<unknown>> = ({ children })
notifications,
]);
const PROMPT_CONTEXTS = useFindPromptContexts({
context: {
isAssistantEnabled:
hasEnterpriseLicence &&
assistantAvailability.isAssistantEnabled &&
assistantAvailability.hasAssistantPrivilege,
httpFetch: http.fetch,
toasts: notifications.toasts,
},
params: {
prompt_group_id: 'aiAssistant',
prompt_ids: ['alertEvaluation', 'dataQualityAnalysis', 'ruleAnalysis'],
},
});
const promptContext = useObservable(
elasticAssistantSharedState.promptContexts.getPromptContext$(),
{}
);
useEffect(() => {
if (isEmpty(promptContext)) {
elasticAssistantSharedState.promptContexts.setPromptContext(PROMPT_CONTEXTS);
}
}, [elasticAssistantSharedState.promptContexts, promptContext, PROMPT_CONTEXTS]);
if (!assistantContextValue) {
return null;
}

View file

@ -1558,10 +1558,17 @@ export const RULE_MANAGEMENT_CONTEXT_DESCRIPTION = i18n.translate(
);
export const EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS = i18n.translate(
'xpack.securitySolution.detectionEngine.ruleManagement.explainThenSummarizeRuleDetails',
'xpack.securitySolution.detectionEngine.ruleManagement.explainThenSummarizeRuleDetailsV2',
{
defaultMessage:
"Please explain the selected rules above. For each rule, highlight why they are relevant, the query as published on Elastic's detection rules repository and an in-depth explanation of it, and what they typically mean for an organization if detected.",
'Please provide a comprehensive analysis of each selected Elastic Security detection rule. For each rule, include:\n' +
'- The rule name and a brief summary of its purpose.\n' +
'- The full detection query as published in Elastics official detection rules repository.\n' +
'- An in-depth explanation of how the query works, including key fields, logic, and detection techniques.\n' +
'- The relevance of the rule to modern threats or attack techniques (e.g., MITRE ATT&CK mapping).\n' +
'- Typical implications and recommended response actions for an organization if this rule triggers.\n' +
'- Any notable false positive considerations or tuning recommendations.\n' +
'Format your response using markdown with clear headers for each rule, code blocks for queries, and concise bullet points for explanations.',
}
);

View file

@ -10,7 +10,7 @@ import { renderHook } from '@testing-library/react';
import type { UseAssistantParams, UseAssistantResult } from './use_assistant';
import { useAssistant } from './use_assistant';
import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser';
import { useAssistantOverlay } from '@kbn/elastic-assistant';
import { useAssistantContext, useAssistantOverlay } from '@kbn/elastic-assistant';
import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability';
jest.mock('../../../../assistant/use_assistant_availability');
@ -23,11 +23,11 @@ const renderUseAssistant = () =>
renderHook((props: UseAssistantParams) => useAssistant(props), {
initialProps: { dataFormattedForFieldBrowser, isAlert },
});
const useAssistantOverlayMock = useAssistantOverlay as jest.Mock;
describe('useAssistant', () => {
let hookResult: RenderHookResult<UseAssistantResult, UseAssistantParams>;
it(`should return showAssistant true and a value for promptContextId`, () => {
beforeEach(() => {
jest.clearAllMocks();
jest.mocked(useAssistantAvailability).mockReturnValue({
hasSearchAILakeConfigurations: false,
hasAssistantPrivilege: true,
@ -37,12 +37,44 @@ describe('useAssistant', () => {
hasManageGlobalKnowledgeBase: true,
isAssistantEnabled: true,
});
jest
.mocked(useAssistantOverlay)
.mockReturnValue({ showAssistantOverlay: jest.fn, promptContextId: '123' });
useAssistantOverlayMock.mockReturnValue({
showAssistantOverlay: jest.fn,
promptContextId: '123',
});
(useAssistantContext as jest.Mock).mockReturnValue({
basePromptContexts: [
{
category: 'alert',
description: 'Alert (from view)',
suggestedUserPrompt: 'ALERT EVALUATION',
tooltip: 'Add this alert as context',
},
{
category: 'data-quality-dashboard',
description: 'Data Quality (index)',
suggestedUserPrompt: 'DATA QUALITY ANALYSIS',
tooltip: 'Add this Data Quality report as context',
},
{
category: 'detection-rules',
description: 'Selected Detection Rules',
suggestedUserPrompt: 'RULE ANALYSIS',
tooltip: 'Add this alert as context',
},
{
category: 'event',
description: 'Event (from view)',
suggestedUserPrompt: 'EVENT EVALUATION',
tooltip: 'Add this event as context',
},
],
});
});
let hookResult: RenderHookResult<UseAssistantResult, UseAssistantParams>;
it(`should return showAssistant true and a value for promptContextId`, () => {
hookResult = renderUseAssistant();
expect(hookResult.result.current.showAssistant).toEqual(true);
expect(hookResult.result.current.promptContextId).toEqual('123');
});
@ -57,9 +89,6 @@ describe('useAssistant', () => {
hasManageGlobalKnowledgeBase: true,
isAssistantEnabled: true,
});
jest
.mocked(useAssistantOverlay)
.mockReturnValue({ showAssistantOverlay: jest.fn, promptContextId: '123' });
hookResult = renderUseAssistant();
@ -68,19 +97,6 @@ describe('useAssistant', () => {
});
it('returns anonymized prompt context data', async () => {
jest.mocked(useAssistantAvailability).mockReturnValue({
hasSearchAILakeConfigurations: false,
hasAssistantPrivilege: true,
hasConnectorsAllPrivilege: true,
hasConnectorsReadPrivilege: true,
hasUpdateAIAssistantAnonymization: true,
hasManageGlobalKnowledgeBase: true,
isAssistantEnabled: true,
});
jest
.mocked(useAssistantOverlay)
.mockReturnValue({ showAssistantOverlay: jest.fn, promptContextId: '123' });
hookResult = renderUseAssistant();
const getPromptContext = (useAssistantOverlay as jest.Mock).mock.calls[0][3];
@ -105,4 +121,16 @@ describe('useAssistant', () => {
'user.name': ['user-name'],
});
});
it('returns correct prompt for alert', () => {
renderUseAssistant();
expect(useAssistantOverlayMock.mock.calls[0][0]).toEqual('alert');
expect(useAssistantOverlayMock.mock.calls[0][5]).toEqual('ALERT EVALUATION');
});
it('returns correct prompt for event', () => {
renderHook((props: UseAssistantParams) => useAssistant(props), {
initialProps: { dataFormattedForFieldBrowser, isAlert: false },
});
expect(useAssistantOverlayMock.mock.calls[0][0]).toEqual('event');
expect(useAssistantOverlayMock.mock.calls[0][5]).toEqual('EVENT EVALUATION');
});
});

View file

@ -6,7 +6,7 @@
*/
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { useAssistantOverlay } from '@kbn/elastic-assistant';
import { useAssistantContext, useAssistantOverlay } from '@kbn/elastic-assistant';
import { useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability';
@ -22,7 +22,6 @@ import {
import {
PROMPT_CONTEXT_ALERT_CATEGORY,
PROMPT_CONTEXT_EVENT_CATEGORY,
PROMPT_CONTEXTS,
} from '../../../../assistant/content/prompt_contexts';
const SUMMARY_VIEW = i18n.translate('xpack.securitySolution.eventDetails.summaryView', {
@ -68,6 +67,15 @@ export const useAssistant = ({
isAlert,
}: UseAssistantParams): UseAssistantResult => {
const { hasAssistantPrivilege, isAssistantEnabled } = useAssistantAvailability();
const { basePromptContexts } = useAssistantContext();
const suggestedUserPrompt = useMemo(
() =>
basePromptContexts.find(
({ category }) =>
category === (isAlert ? PROMPT_CONTEXT_ALERT_CATEGORY : PROMPT_CONTEXT_EVENT_CATEGORY)
)?.suggestedUserPrompt,
[basePromptContexts, isAlert]
);
const useAssistantHook = hasAssistantPrivilege ? useAssistantOverlay : useAssistantNoop;
const getPromptContext = useCallback(
async () => getRawData(dataFormattedForFieldBrowser ?? []),
@ -93,9 +101,7 @@ export const useAssistant = ({
: EVENT_SUMMARY_CONTEXT_DESCRIPTION(SUMMARY_VIEW),
getPromptContext,
null,
isAlert
? PROMPT_CONTEXTS[PROMPT_CONTEXT_ALERT_CATEGORY].suggestedUserPrompt
: PROMPT_CONTEXTS[PROMPT_CONTEXT_EVENT_CATEGORY].suggestedUserPrompt,
suggestedUserPrompt,
isAlert ? ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP : EVENT_SUMMARY_VIEW_CONTEXT_TOOLTIP,
isAssistantEnabled
);

View file

@ -65,7 +65,6 @@ import { PluginServices } from './plugin_services';
import { getExternalReferenceAttachmentEndpointRegular } from './cases/attachments/external_reference';
import { isSecuritySolutionAccessible } from './helpers_access';
import { generateAttachmentType } from './threat_intelligence/modules/cases/utils/attachments';
import { PROMPT_CONTEXTS } from './assistant/content/prompt_contexts';
export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {
private config: SecuritySolutionUiConfigType;
@ -137,9 +136,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
const subPluginRoutes = getSubPluginRoutesByCapabilities(subPlugins, services);
const unmountPromptContext =
services.elasticAssistantSharedState.promptContexts.setPromptContext(PROMPT_CONTEXTS);
const unmountApp = renderApp({
...params,
services,
@ -150,7 +146,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
return () => {
unmountApp();
unmountPromptContext();
};
},
});

View file

@ -5,11 +5,7 @@
* 2.0.
*/
import {
EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS,
RULE_MANAGEMENT_CONTEXT_DESCRIPTION,
} from '@kbn/security-solution-plugin/public/detection_engine/common/translations';
import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N } from '@kbn/security-solution-plugin/public/assistant/content/prompts/user/translations';
import { RULE_MANAGEMENT_CONTEXT_DESCRIPTION } from '@kbn/security-solution-plugin/public/detection_engine/common/translations';
import { IS_SERVERLESS } from '../../env_var_names_constants';
import {
assertConnectorSelected,
@ -94,7 +90,6 @@ describe('AI Assistant Conversations', { tags: ['@ess', '@serverless'] }, () =>
openAssistant('rule');
assertNewConversation(false, `Detection Rules - Rule 1`);
assertConnectorSelected(azureConnectorAPIPayload.name);
cy.get(USER_PROMPT).should('have.text', EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS);
cy.get(PROMPT_CONTEXT_BUTTON(0)).should('have.text', RULE_MANAGEMENT_CONTEXT_DESCRIPTION);
});
});
@ -106,10 +101,6 @@ describe('AI Assistant Conversations', { tags: ['@ess', '@serverless'] }, () =>
openAssistant('alert');
assertConversationTitleContains('New Rule Test');
assertConnectorSelected(azureConnectorAPIPayload.name);
cy.get(USER_PROMPT).should(
'have.text',
EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N
);
cy.get(PROMPT_CONTEXT_BUTTON(0)).should('have.text', 'Alert (from summary)');
});
it('Shows empty connector callout when a conversation that had a connector no longer does', () => {
@ -138,7 +129,6 @@ describe('AI Assistant Conversations', { tags: ['@ess', '@serverless'] }, () =>
assertConversationTitleContains('New Rule Test');
// send message to ensure conversation is created
submitMessage();
assertMessageSent(EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N);
closeAssistant();
visitGetStartedPage();
openAssistant();

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N } from '@kbn/security-solution-plugin/public/assistant/content/prompts/user/translations';
import { PromptCreateProps } from '@kbn/elastic-assistant-common/impl/schemas';
import { IS_SERVERLESS } from '../../env_var_names_constants';
import { QUICK_PROMPT_BADGE, USER_PROMPT } from '../../screens/ai_assistant';
@ -182,10 +181,6 @@ describe('AI Assistant Prompts', { tags: ['@ess', '@serverless'] }, () => {
expandFirstAlert();
openAssistant('alert');
cy.get(QUICK_PROMPT_BADGE(testPrompt.name)).should('be.visible');
cy.get(USER_PROMPT).should(
'have.text',
EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N
);
cy.get(QUICK_PROMPT_BADGE(testPrompt.name)).click();
cy.get(USER_PROMPT).should('have.text', testPrompt.content);
});