AttackDiscoveryClient¶
Client for the Kibana Security Attack Discovery API.
Attack discovery analyzes security alerts with AI (through a Kibana Gen AI connector) and surfaces potential attack chains as “Attack discoveries”. The client covers ad-hoc generation, discovery search and bulk updates, generation-run tracking, and Attack Discovery schedules (Kibana 9.4).
Attack Discovery resources are space-scoped: every method accepts an optional
space_id to target a specific space. The securitySolutionAttackDiscovery
feature must be enabled in the target space – spaces using the pure
Elasticsearch solution view disable it (there, find_schedules reports
total: 0 even though schedules exist). Prefer a space created with
solution="security".
- class kibana._sync.client.attack_discovery.AttackDiscoveryClient(client, default_space_id=None, validate_spaces=True)[source]¶
Bases:
NamespaceClientClient for the Kibana Security Attack Discovery API.
Attack discovery uses AI (via a Kibana Gen AI connector) to analyze security alerts and surface potential attack chains as “Attack discoveries”. This client covers the full 9.4 public surface:
ad-hoc discovery generation (
generate), retrieval (find) and bulk workflow-status/visibility updates (bulk_update),generation runs metadata (
get_generations,get_generation,dismiss_generation),Attack Discovery schedules CRUD plus enable/disable and search (
create_schedule,get_schedule,update_schedule,delete_schedule,enable_schedule,disable_schedule,find_schedules).
All Attack Discovery resources are space-scoped: every method accepts an optional
space_idto target a specific space (Nonetargets the default space or the space the client is scoped to).Note
The Attack Discovery Kibana feature must be enabled in the target space. Spaces using the pure Elasticsearch solution view (for example, a default space created with
solution: "es") disable thesecuritySolutionAttackDiscoveryfeature: in such spacesfind_schedulesreturnstotal: 0even though schedule create/get/update/delete/enable/disable still work. Use a space with the security solution view (or the classic view) for full functionality.Note
For OpenAI-compatible connectors (
.gen-ai), the connector’sapiUrlmust be the full chat-completions endpoint (for examplehttp://localhost:1234/v1/chat/completions), not just the API base URL.Example
>>> from kibana import Kibana >>> client = Kibana("http://localhost:5601", api_key="...") >>> >>> # Kick off an ad-hoc generation with a Gen AI connector >>> resp = client.attack_discovery.generate( ... alerts_index_pattern=".alerts-security.alerts-default", ... anonymization_fields=[ ... {"id": "f1", "field": "_id", "allowed": True, "anonymized": False}, ... {"id": "f2", "field": "host.name", "allowed": True, "anonymized": True}, ... ], ... api_config={"actionTypeId": ".gen-ai", "connectorId": "my-connector"}, ... size=25, ... ) >>> execution_uuid = resp.body["execution_uuid"] >>> >>> # Poll the generation until it completes >>> gen = client.attack_discovery.get_generation(execution_uuid=execution_uuid) >>> print(gen.body["generation"]["status"])
Generating Attack Discoveries
Generation needs a Gen AI connector. For OpenAI-compatible backends the connector’s
apiUrlmust be the full/chat/completionsendpoint. The_idfield must be allowed in the anonymization fields:import time from kibana import Kibana client = Kibana("http://localhost:5601", api_key="your_api_key") connector = client.connectors.create( name="My GenAI connector", connector_type_id=".gen-ai", config={ "apiProvider": "OpenAI", "apiUrl": "http://localhost:1234/v1/chat/completions", "defaultModel": "qwen/qwen3.5-9b", }, secrets={"apiKey": "dummy-key"}, ) started = client.attack_discovery.generate( alerts_index_pattern=".alerts-security.alerts-default", anonymization_fields=[ {"id": "f0", "field": "_id", "allowed": True, "anonymized": False}, {"id": "f1", "field": "host.name", "allowed": True, "anonymized": True}, {"id": "f2", "field": "user.name", "allowed": True, "anonymized": True}, ], api_config={ "actionTypeId": ".gen-ai", "connectorId": connector.body["id"], }, size=100, start="now-24h", end="now", ) execution_uuid = started.body["execution_uuid"] # Poll the generation until it completes (an LLM run can take minutes) while True: generation = client.attack_discovery.get_generation( execution_uuid=execution_uuid ).body["generation"] if generation["status"] in ("succeeded", "failed", "canceled"): break time.sleep(10)
Searching and Updating Discoveries
# Search discoveries from the last day found = client.attack_discovery.find( start="now-24h", end="now", status=["open"], per_page=25 ) for discovery in found.body["data"]: print(discovery["id"], discovery.get("title")) # Acknowledge them in bulk ids = [d["id"] for d in found.body["data"]] if ids: client.attack_discovery.bulk_update( ids=ids, kibana_alert_workflow_status="acknowledged" ) # List generation runs; dismiss one generations = client.attack_discovery.get_generations(size=20) client.attack_discovery.dismiss_generation(execution_uuid=execution_uuid)
Managing Schedules
Schedules run attack discovery generation periodically. Their
paramscarry the alerts index pattern, the LLM connector configuration and the maximum number of alerts:created = client.attack_discovery.create_schedule( name="Daily attack discovery", params={ "alerts_index_pattern": ".alerts-security.alerts-default", "api_config": { "connectorId": connector.body["id"], "actionTypeId": ".gen-ai", "name": "My GenAI connector", }, "size": 100, }, schedule={"interval": "24h"}, ) schedule_id = created.body["id"] client.attack_discovery.enable_schedule(id=schedule_id) client.attack_discovery.disable_schedule(id=schedule_id) # Full update (all properties are required) client.attack_discovery.update_schedule( id=schedule_id, name="Daily attack discovery (100 alerts)", params=created.body["params"], schedule={"interval": "12h"}, actions=[], ) found = client.attack_discovery.find_schedules(per_page=100) client.attack_discovery.delete_schedule(id=schedule_id)
- __init__(client, default_space_id=None, validate_spaces=True)[source]¶
Initialize the AttackDiscoveryClient.
- Parameters:
Example
>>> attack_discovery_client = AttackDiscoveryClient(kibana_client)
- find(*, alert_ids=None, connector_names=None, enable_field_rendering=None, end=None, ids=None, include_unique_alert_ids=None, page=None, per_page=None, search=None, scheduled=None, shared=None, sort_field=None, sort_order=None, start=None, status=None, with_replacements=None, space_id=None, validate_spaces=None)[source]¶
Find Attack discoveries that match the search criteria.
Supports free text search, filtering, pagination, and sorting (
GET /api/attack_discovery/_find).- Parameters:
alert_ids (list[str] | None) – Filter results to Attack discoveries that include any of the provided alert IDs.
connector_names (list[str] | None) – Filter results to Attack discoveries created by any of the provided human readable connector names (the
connector_nameproperty, e.g."GPT-5 Chat"), which are distinct fromconnector_idvalues.enable_field_rendering (bool | None) – Enables a markdown syntax used to render pivot fields, for example
{{ user.name james }}. Defaults toFalseon the server.end (str | None) – End of the time range for the search. Accepts absolute timestamps (ISO 8601) or relative date math (e.g.
"now").ids (list[str] | None) – Filter results to the Attack discoveries with the specified IDs.
include_unique_alert_ids (bool | None) – If True, the response includes
unique_alert_idsandunique_alert_ids_countaggregated across the matched Attack discoveries.page (int | None) – Page number to return. Defaults to 1 on the server.
per_page (int | None) – Number of Attack discoveries per page. Defaults to 10 on the server.
search (str | None) – Free-text search query applied to relevant text fields (title, description, tags, etc.).
scheduled (bool | None) – Use True to return only scheduled discoveries, False for only ad-hoc discoveries; omit for both.
shared (bool | None) – Use True to return only shared discoveries, False for only those visible to the current user; omit for both.
sort_field (str | None) – Field used to sort results. In 9.4 only
"@timestamp"is allowed.sort_order (str | None) – Sort direction,
"asc"or"desc"(server default"desc").start (str | None) – Start of the time range for the search (ISO 8601 or date math such as
"now-24h").status (list[str] | None) – Filter by alert workflow status; one or more of
"open","acknowledged","closed".with_replacements (bool | None) – When True, returns discoveries with text replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title fields. Defaults to
Trueon the server.space_id (str | None) – Optional space ID to search in.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse containing
connector_names,data(list of Attack discovery alerts),page,per_page,totalandunique_alert_ids_count.- Raises:
BadRequestError – If the query parameters are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> found = client.attack_discovery.find( ... start="now-24h", end="now", status=["open"], per_page=10 ... ) >>> print(found.body["total"])
- bulk_update(*, ids, enable_field_rendering=None, kibana_alert_workflow_status=None, visibility=None, with_replacements=None, space_id=None, validate_spaces=None)[source]¶
Bulk update Attack discoveries.
Performs bulk updates on multiple Attack discoveries, including workflow status changes and visibility settings, without requiring individual API calls for each alert (
POST /api/attack_discovery/_bulk).- Parameters:
enable_field_rendering (bool | None) – Enables a markdown syntax used to render pivot fields, for example
{{ user.name james }}. Defaults toFalseon the server.kibana_alert_workflow_status (str | None) – When provided, updates the
kibana.alert.workflow_statusof the attack discovery alerts; one of"open","acknowledged","closed".visibility (str | None) – When provided, updates the visibility of the alert; one of
"not_shared","shared".with_replacements (bool | None) – When True, returns the updated discoveries with text replacements applied. Defaults to
Trueon the server.space_id (str | None) – Optional space ID to update discoveries in.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
the array of updated Attack Discovery alert objects. IDs that match no existing discovery are silently ignored (the live server returns
{"data": []}rather than an error).- Return type:
ObjectApiResponse with
data- Raises:
BadRequestError – If the request parameters are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
Example
>>> updated = client.attack_discovery.bulk_update( ... ids=["c0c8a8bb..."], ... kibana_alert_workflow_status="acknowledged", ... ) >>> print(len(updated.body["data"]))
- generate(*, alerts_index_pattern, anonymization_fields, api_config, size, sub_action='invokeAI', connector_name=None, end=None, filter=None, model=None, replacements=None, start=None, space_id=None, validate_spaces=None)[source]¶
Generate attack discoveries from alerts.
Initiates the generation of attack discoveries by analyzing security alerts using AI. Returns an execution UUID that can be used to track the generation progress and retrieve results (
POST /api/attack_discovery/_generate). Results may also be retrieved viafind().Note
The
_idfield must be present (andallowed) inanonymization_fields; otherwise the generation fails with “The _id field must be allowed to generate Attack discoveries.” The alerts index is sorted onkibana.alert.risk_score, so a custom index pattern must have a mapping for that field.- Parameters:
alerts_index_pattern (str) – The (space specific) index pattern that contains the alerts to use as context, e.g.
".alerts-security.alerts-default".anonymization_fields (list[dict[str, Any]]) – The list of fields, and whether or not they are anonymized, allowed to be sent to LLMs. Each entry has
id,field,allowedandanonymizedkeys. Consider using the output of the/api/security_ai_assistant/anonymization_fields/_findAPI.api_config (dict[str, Any]) – LLM API configuration with required
connectorIdandactionTypeIdkeys (optionallymodel,provider,defaultSystemPromptId).size (int) – The maximum number of alerts to analyze.
sub_action (str) – LLM invocation mode,
"invokeAI"(default) or"invokeStream".connector_name (str | None) – Optional human readable connector name recorded on the generation.
end (str | None) – End of the alert time range (ISO 8601 or date math, e.g.
"now").filter (dict[str, Any] | None) – An Elasticsearch query DSL object used to filter alerts.
model (str | None) – Optional model override.
replacements (dict[str, str] | None) – Optional anonymization replacements mapping (anonymized value -> original value).
start (str | None) – Start of the alert time range (e.g.
"now-24h").space_id (str | None) – Optional space ID to generate discoveries in.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
the identifier for the attack discovery generation process, usable with
get_generation()anddismiss_generation().- Return type:
ObjectApiResponse with
execution_uuid- Raises:
BadRequestError – If the generation configuration is invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
Example
>>> resp = client.attack_discovery.generate( ... alerts_index_pattern=".alerts-security.alerts-default", ... anonymization_fields=[ ... {"id": "f1", "field": "_id", "allowed": True, ... "anonymized": False}, ... ], ... api_config={ ... "actionTypeId": ".gen-ai", ... "connectorId": "my-connector-id", ... }, ... size=25, ... start="now-24h", ... end="now", ... ) >>> print(resp.body["execution_uuid"])
- get_generations(*, end=None, size=None, start=None, space_id=None, validate_spaces=None)[source]¶
Get the latest Attack Discovery generations metadata.
Retrieves generation metadata for the current user, including execution status and statistics (
GET /api/attack_discovery/generations).- Parameters:
end (str | None) – End of the time range for filtering generations (ISO 8601 or date math, e.g.
"now").size (int | None) – The maximum number of generations to retrieve (server default 50).
start (str | None) – Start of the time range for filtering generations (e.g.
"now-24h").space_id (str | None) – Optional space ID to list generations from.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
a list of generation metadata objects (
execution_uuid,status,connector_id,discoveries, timing fields, …). Note: despite the API description, on a live 9.4.3 server dismissed generations are still listed, withstatus: "dismissed".- Return type:
ObjectApiResponse with
generations- Raises:
BadRequestError – If the query parameters are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
Example
>>> gens = client.attack_discovery.get_generations(size=10) >>> for g in gens.body["generations"]: ... print(g["execution_uuid"], g["status"])
- get_generation(*, execution_uuid, enable_field_rendering=None, with_replacements=None, space_id=None, validate_spaces=None)[source]¶
Get a single Attack Discovery generation.
Returns a specific generation, including all generated Attack discoveries and associated metadata such as execution status and statistics (
GET /api/attack_discovery/generations/{execution_uuid}).- Parameters:
execution_uuid (str) – The unique identifier for the generation execution, as returned by
generate().enable_field_rendering (bool | None) – Enables a markdown syntax used to render pivot fields, for example
{{ user.name james }}. Defaults toFalseon the server.with_replacements (bool | None) – When True, returns discoveries with text replacements applied. Defaults to
Trueon the server.space_id (str | None) – Optional space ID to get the generation from.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse with
generation(metadata includingstatus: one ofstarted,succeeded,failed,dismissed,canceled) anddata(the generated Attack discoveries).- Raises:
NotFoundError – If no generation exists for the execution UUID.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> gen = client.attack_discovery.get_generation( ... execution_uuid="edd26039-0990-4d9f-9829-2a1fcacb77b5" ... ) >>> print(gen.body["generation"]["status"])
- dismiss_generation(*, execution_uuid, space_id=None, validate_spaces=None)[source]¶
Dismiss an Attack Discovery generation.
Marks a generation as dismissed for the current user (
POST /api/attack_discovery/generations/{execution_uuid}/_dismiss).- Parameters:
execution_uuid (str) – The unique identifier for the generation execution, as returned by
generate().space_id (str | None) – Optional space ID the generation belongs to.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse with the (dismissed) generation metadata object. The updated
dismissedstatus becomes visible inget_generations()shortly after (event-log refresh).- Raises:
NotFoundError – If no generation exists for the execution UUID.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> client.attack_discovery.dismiss_generation( ... execution_uuid="edd26039-0990-4d9f-9829-2a1fcacb77b5" ... )
- create_schedule(*, name, params, schedule, actions=None, enabled=None, space_id=None, validate_spaces=None)[source]¶
Create an Attack Discovery schedule.
Creates a schedule that periodically generates attack discoveries (
POST /api/attack_discovery/schedules). A schedule is backed by an alerting rule of typeattack-discovery.- Parameters:
name (str) – The name of the schedule.
params (dict[str, Any]) – The schedule configuration parameters. Required keys:
alerts_index_pattern(index pattern to get alerts from),api_config(LLM configuration withconnectorId,actionTypeIdand the connectorname) andsize(max number of alerts). Optional:query,filters,combined_filter,start,end.schedule (dict[str, Any]) – The schedule interval, e.g.
{"interval": "24h"}.actions (list[dict[str, Any]] | None) – Optional schedule actions (connector notifications).
enabled (bool | None) – Whether the schedule is enabled (server default False).
space_id (str | None) – Optional space ID to create the schedule in.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse with the created schedule (
id,name,created_by,updated_by,created_at,updated_at,enabled,params,schedule,actions).- Raises:
BadRequestError – If the schedule properties are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> created = client.attack_discovery.create_schedule( ... name="Daily attack discovery", ... params={ ... "alerts_index_pattern": ".alerts-security.alerts-default", ... "api_config": { ... "connectorId": "my-connector-id", ... "actionTypeId": ".gen-ai", ... "name": "My GenAI connector", ... }, ... "size": 100, ... }, ... schedule={"interval": "24h"}, ... ) >>> schedule_id = created.body["id"]
- get_schedule(*, id, space_id=None, validate_spaces=None)[source]¶
Get an Attack Discovery schedule by ID.
Retrieves a single schedule (
GET /api/attack_discovery/schedules/{id}).- Parameters:
- Returns:
ObjectApiResponse with the schedule details (
id,name,enabled,params,schedule,actions, audit fields).- Raises:
NotFoundError – If the schedule does not exist.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> schedule = client.attack_discovery.get_schedule(id="b0ab787f-...") >>> print(schedule.body["name"])
- update_schedule(*, id, name, params, schedule, actions, space_id=None, validate_spaces=None)[source]¶
Update an Attack Discovery schedule.
Replaces the schedule’s properties (
PUT /api/attack_discovery/schedules/{id}). All properties are required by the 9.4 API (this is a full update, not a patch).- Parameters:
id (str) – The identifier of the schedule to update.
name (str) – The name of the schedule.
params (dict[str, Any]) – The schedule configuration parameters (see
create_schedule()for the required keys).schedule (dict[str, Any]) – The schedule interval, e.g.
{"interval": "12h"}.actions (list[dict[str, Any]]) – The schedule actions. Pass
[]to keep no actions.space_id (str | None) – Optional space ID the schedule belongs to.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse with the updated schedule details.
- Raises:
NotFoundError – If the schedule does not exist.
BadRequestError – If the schedule properties are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> updated = client.attack_discovery.update_schedule( ... id="b0ab787f-...", ... name="Renamed schedule", ... params={ ... "alerts_index_pattern": ".alerts-security.alerts-default", ... "api_config": { ... "connectorId": "my-connector-id", ... "actionTypeId": ".gen-ai", ... "name": "My GenAI connector", ... }, ... "size": 50, ... }, ... schedule={"interval": "12h"}, ... actions=[], ... )
- delete_schedule(*, id, space_id=None, validate_spaces=None)[source]¶
Delete an Attack Discovery schedule.
Deletes the schedule and its backing alerting rule (
DELETE /api/attack_discovery/schedules/{id}).- Parameters:
- Returns:
ObjectApiResponse with the deleted schedule’s
id.- Raises:
NotFoundError – If the schedule does not exist.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> client.attack_discovery.delete_schedule(id="b0ab787f-...")
- enable_schedule(*, id, space_id=None, validate_spaces=None)[source]¶
Enable an Attack Discovery schedule.
Starts periodic attack discovery generation for the schedule (
POST /api/attack_discovery/schedules/{id}/_enable).- Parameters:
- Returns:
ObjectApiResponse with the schedule’s
id.- Raises:
NotFoundError – If the schedule does not exist.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> client.attack_discovery.enable_schedule(id="b0ab787f-...")
- disable_schedule(*, id, space_id=None, validate_spaces=None)[source]¶
Disable an Attack Discovery schedule.
Stops periodic attack discovery generation for the schedule (
POST /api/attack_discovery/schedules/{id}/_disable).- Parameters:
- Returns:
ObjectApiResponse with the schedule’s
id.- Raises:
NotFoundError – If the schedule does not exist.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> client.attack_discovery.disable_schedule(id="b0ab787f-...")
- find_schedules(*, page=None, per_page=None, sort_direction=None, sort_field=None, space_id=None, validate_spaces=None)[source]¶
Find Attack Discovery schedules.
Lists schedules with pagination and sorting (
GET /api/attack_discovery/schedules/_find).Note
On a live 9.4.3 server the
pageparameter is off by one: the server treats an explicitpage=Nas pageN + 1(page=1returns the second page). Omitpage(or passpage=0) to get the first page. Also, in spaces where thesecuritySolutionAttackDiscoveryfeature is disabled (e.g. spaces with the Elasticsearch solution view), this endpoint returnstotal: 0even though schedules exist.- Parameters:
page (int | None) – Page number (see the note above about the live off-by-one behavior).
per_page (int | None) – Number of schedules per page (server default 10).
sort_direction (str | None) – Sort direction,
"asc"or"desc"(server default"asc").sort_field (str | None) – Field to sort by. Common fields:
"name","created_at","updated_at","enabled".space_id (str | None) – Optional space ID to search in.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse with
page,per_page,totalanddata(the list of schedules).- Raises:
BadRequestError – If the query parameters are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> found = client.attack_discovery.find_schedules(per_page=100) >>> for s in found.body["data"]: ... print(s["id"], s["name"], s["enabled"])
AsyncAttackDiscoveryClient¶
Asynchronous version of the AttackDiscoveryClient for use with async/await syntax.
- class kibana._async.client.attack_discovery.AsyncAttackDiscoveryClient(client, default_space_id=None, validate_spaces=True)[source]¶
Bases:
AsyncNamespaceClientAsync client for the Kibana Security Attack Discovery API.
Attack discovery uses AI (via a Kibana Gen AI connector) to analyze security alerts and surface potential attack chains as “Attack discoveries”. This client covers the full 9.4 public surface:
ad-hoc discovery generation (
generate), retrieval (find) and bulk workflow-status/visibility updates (bulk_update),generation runs metadata (
get_generations,get_generation,dismiss_generation),Attack Discovery schedules CRUD plus enable/disable and search (
create_schedule,get_schedule,update_schedule,delete_schedule,enable_schedule,disable_schedule,find_schedules).
All Attack Discovery resources are space-scoped: every method accepts an optional
space_idto target a specific space (Nonetargets the default space or the space the client is scoped to).Note
The Attack Discovery Kibana feature must be enabled in the target space. Spaces using the pure Elasticsearch solution view (for example, a default space created with
solution: "es") disable thesecuritySolutionAttackDiscoveryfeature: in such spacesfind_schedulesreturnstotal: 0even though schedule create/get/update/delete/enable/disable still work. Use a space with the security solution view (or the classic view) for full functionality.Note
For OpenAI-compatible connectors (
.gen-ai), the connector’sapiUrlmust be the full chat-completions endpoint (for examplehttp://localhost:1234/v1/chat/completions), not just the API base URL.Example
>>> from kibana import AsyncKibana >>> client = AsyncKibana("http://localhost:5601", api_key="...") >>> >>> # Kick off an ad-hoc generation with a Gen AI connector >>> resp = await client.attack_discovery.generate( ... alerts_index_pattern=".alerts-security.alerts-default", ... anonymization_fields=[ ... {"id": "f1", "field": "_id", "allowed": True, "anonymized": False}, ... {"id": "f2", "field": "host.name", "allowed": True, "anonymized": True}, ... ], ... api_config={"actionTypeId": ".gen-ai", "connectorId": "my-connector"}, ... size=25, ... ) >>> execution_uuid = resp.body["execution_uuid"] >>> >>> # Poll the generation until it completes >>> gen = await client.attack_discovery.get_generation(execution_uuid=execution_uuid) >>> print(gen.body["generation"]["status"])
Usage
The AsyncAttackDiscoveryClient provides the same methods as AttackDiscoveryClient but all methods are async and must be awaited:
import asyncio from kibana import AsyncKibana async def main(): async with AsyncKibana("http://localhost:5601") as client: # Kick off a generation (async) started = await client.attack_discovery.generate( alerts_index_pattern=".alerts-security.alerts-default", anonymization_fields=[ {"id": "f0", "field": "_id", "allowed": True, "anonymized": False}, ], api_config={ "actionTypeId": ".gen-ai", "connectorId": "my-connector-id", }, size=100, ) # Track it (async) generation = await client.attack_discovery.get_generation( execution_uuid=started.body["execution_uuid"] ) # Search discoveries (async) found = await client.attack_discovery.find( start="now-24h", end="now" ) asyncio.run(main())
- __init__(client, default_space_id=None, validate_spaces=True)[source]¶
Initialize the AsyncAttackDiscoveryClient.
- Parameters:
client (AsyncKibana) – The parent AsyncKibana client instance to delegate requests to.
default_space_id (str | None) – Optional default space ID for all operations.
validate_spaces (bool) – Whether to validate space existence before space-scoped operations (default: True).
Example
>>> attack_discovery_client = AsyncAttackDiscoveryClient(kibana_client)
- async find(*, alert_ids=None, connector_names=None, enable_field_rendering=None, end=None, ids=None, include_unique_alert_ids=None, page=None, per_page=None, search=None, scheduled=None, shared=None, sort_field=None, sort_order=None, start=None, status=None, with_replacements=None, space_id=None, validate_spaces=None)[source]¶
Find Attack discoveries that match the search criteria.
Supports free text search, filtering, pagination, and sorting (
GET /api/attack_discovery/_find).- Parameters:
alert_ids (list[str] | None) – Filter results to Attack discoveries that include any of the provided alert IDs.
connector_names (list[str] | None) – Filter results to Attack discoveries created by any of the provided human readable connector names (the
connector_nameproperty, e.g."GPT-5 Chat"), which are distinct fromconnector_idvalues.enable_field_rendering (bool | None) – Enables a markdown syntax used to render pivot fields, for example
{{ user.name james }}. Defaults toFalseon the server.end (str | None) – End of the time range for the search. Accepts absolute timestamps (ISO 8601) or relative date math (e.g.
"now").ids (list[str] | None) – Filter results to the Attack discoveries with the specified IDs.
include_unique_alert_ids (bool | None) – If True, the response includes
unique_alert_idsandunique_alert_ids_countaggregated across the matched Attack discoveries.page (int | None) – Page number to return. Defaults to 1 on the server.
per_page (int | None) – Number of Attack discoveries per page. Defaults to 10 on the server.
search (str | None) – Free-text search query applied to relevant text fields (title, description, tags, etc.).
scheduled (bool | None) – Use True to return only scheduled discoveries, False for only ad-hoc discoveries; omit for both.
shared (bool | None) – Use True to return only shared discoveries, False for only those visible to the current user; omit for both.
sort_field (str | None) – Field used to sort results. In 9.4 only
"@timestamp"is allowed.sort_order (str | None) – Sort direction,
"asc"or"desc"(server default"desc").start (str | None) – Start of the time range for the search (ISO 8601 or date math such as
"now-24h").status (list[str] | None) – Filter by alert workflow status; one or more of
"open","acknowledged","closed".with_replacements (bool | None) – When True, returns discoveries with text replacements applied to the detailsMarkdown, entitySummaryMarkdown, summaryMarkdown, and title fields. Defaults to
Trueon the server.space_id (str | None) – Optional space ID to search in.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse containing
connector_names,data(list of Attack discovery alerts),page,per_page,totalandunique_alert_ids_count.- Raises:
BadRequestError – If the query parameters are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> found = await client.attack_discovery.find( ... start="now-24h", end="now", status=["open"], per_page=10 ... ) >>> print(found.body["total"])
- async bulk_update(*, ids, enable_field_rendering=None, kibana_alert_workflow_status=None, visibility=None, with_replacements=None, space_id=None, validate_spaces=None)[source]¶
Bulk update Attack discoveries.
Performs bulk updates on multiple Attack discoveries, including workflow status changes and visibility settings, without requiring individual API calls for each alert (
POST /api/attack_discovery/_bulk).- Parameters:
enable_field_rendering (bool | None) – Enables a markdown syntax used to render pivot fields, for example
{{ user.name james }}. Defaults toFalseon the server.kibana_alert_workflow_status (str | None) – When provided, updates the
kibana.alert.workflow_statusof the attack discovery alerts; one of"open","acknowledged","closed".visibility (str | None) – When provided, updates the visibility of the alert; one of
"not_shared","shared".with_replacements (bool | None) – When True, returns the updated discoveries with text replacements applied. Defaults to
Trueon the server.space_id (str | None) – Optional space ID to update discoveries in.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
the array of updated Attack Discovery alert objects. IDs that match no existing discovery are silently ignored (the live server returns
{"data": []}rather than an error).- Return type:
ObjectApiResponse with
data- Raises:
BadRequestError – If the request parameters are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
Example
>>> updated = await client.attack_discovery.bulk_update( ... ids=["c0c8a8bb..."], ... kibana_alert_workflow_status="acknowledged", ... ) >>> print(len(updated.body["data"]))
- async generate(*, alerts_index_pattern, anonymization_fields, api_config, size, sub_action='invokeAI', connector_name=None, end=None, filter=None, model=None, replacements=None, start=None, space_id=None, validate_spaces=None)[source]¶
Generate attack discoveries from alerts.
Initiates the generation of attack discoveries by analyzing security alerts using AI. Returns an execution UUID that can be used to track the generation progress and retrieve results (
POST /api/attack_discovery/_generate). Results may also be retrieved viafind().Note
The
_idfield must be present (andallowed) inanonymization_fields; otherwise the generation fails with “The _id field must be allowed to generate Attack discoveries.” The alerts index is sorted onkibana.alert.risk_score, so a custom index pattern must have a mapping for that field.- Parameters:
alerts_index_pattern (str) – The (space specific) index pattern that contains the alerts to use as context, e.g.
".alerts-security.alerts-default".anonymization_fields (list[dict[str, Any]]) – The list of fields, and whether or not they are anonymized, allowed to be sent to LLMs. Each entry has
id,field,allowedandanonymizedkeys. Consider using the output of the/api/security_ai_assistant/anonymization_fields/_findAPI.api_config (dict[str, Any]) – LLM API configuration with required
connectorIdandactionTypeIdkeys (optionallymodel,provider,defaultSystemPromptId).size (int) – The maximum number of alerts to analyze.
sub_action (str) – LLM invocation mode,
"invokeAI"(default) or"invokeStream".connector_name (str | None) – Optional human readable connector name recorded on the generation.
end (str | None) – End of the alert time range (ISO 8601 or date math, e.g.
"now").filter (dict[str, Any] | None) – An Elasticsearch query DSL object used to filter alerts.
model (str | None) – Optional model override.
replacements (dict[str, str] | None) – Optional anonymization replacements mapping (anonymized value -> original value).
start (str | None) – Start of the alert time range (e.g.
"now-24h").space_id (str | None) – Optional space ID to generate discoveries in.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
the identifier for the attack discovery generation process, usable with
get_generation()anddismiss_generation().- Return type:
ObjectApiResponse with
execution_uuid- Raises:
BadRequestError – If the generation configuration is invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
Example
>>> resp = await client.attack_discovery.generate( ... alerts_index_pattern=".alerts-security.alerts-default", ... anonymization_fields=[ ... {"id": "f1", "field": "_id", "allowed": True, ... "anonymized": False}, ... ], ... api_config={ ... "actionTypeId": ".gen-ai", ... "connectorId": "my-connector-id", ... }, ... size=25, ... start="now-24h", ... end="now", ... ) >>> print(resp.body["execution_uuid"])
- async get_generations(*, end=None, size=None, start=None, space_id=None, validate_spaces=None)[source]¶
Get the latest Attack Discovery generations metadata.
Retrieves generation metadata for the current user, including execution status and statistics (
GET /api/attack_discovery/generations).- Parameters:
end (str | None) – End of the time range for filtering generations (ISO 8601 or date math, e.g.
"now").size (int | None) – The maximum number of generations to retrieve (server default 50).
start (str | None) – Start of the time range for filtering generations (e.g.
"now-24h").space_id (str | None) – Optional space ID to list generations from.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
a list of generation metadata objects (
execution_uuid,status,connector_id,discoveries, timing fields, …). Note: despite the API description, on a live 9.4.3 server dismissed generations are still listed, withstatus: "dismissed".- Return type:
ObjectApiResponse with
generations- Raises:
BadRequestError – If the query parameters are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
Example
>>> gens = client.attack_discovery.get_generations(size=10) >>> for g in gens.body["generations"]: ... print(g["execution_uuid"], g["status"])
- async get_generation(*, execution_uuid, enable_field_rendering=None, with_replacements=None, space_id=None, validate_spaces=None)[source]¶
Get a single Attack Discovery generation.
Returns a specific generation, including all generated Attack discoveries and associated metadata such as execution status and statistics (
GET /api/attack_discovery/generations/{execution_uuid}).- Parameters:
execution_uuid (str) – The unique identifier for the generation execution, as returned by
generate().enable_field_rendering (bool | None) – Enables a markdown syntax used to render pivot fields, for example
{{ user.name james }}. Defaults toFalseon the server.with_replacements (bool | None) – When True, returns discoveries with text replacements applied. Defaults to
Trueon the server.space_id (str | None) – Optional space ID to get the generation from.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse with
generation(metadata includingstatus: one ofstarted,succeeded,failed,dismissed,canceled) anddata(the generated Attack discoveries).- Raises:
NotFoundError – If no generation exists for the execution UUID.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> gen = await client.attack_discovery.get_generation( ... execution_uuid="edd26039-0990-4d9f-9829-2a1fcacb77b5" ... ) >>> print(gen.body["generation"]["status"])
- async dismiss_generation(*, execution_uuid, space_id=None, validate_spaces=None)[source]¶
Dismiss an Attack Discovery generation.
Marks a generation as dismissed for the current user (
POST /api/attack_discovery/generations/{execution_uuid}/_dismiss).- Parameters:
execution_uuid (str) – The unique identifier for the generation execution, as returned by
generate().space_id (str | None) – Optional space ID the generation belongs to.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse with the (dismissed) generation metadata object. The updated
dismissedstatus becomes visible inget_generations()shortly after (event-log refresh).- Raises:
NotFoundError – If no generation exists for the execution UUID.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> await client.attack_discovery.dismiss_generation( ... execution_uuid="edd26039-0990-4d9f-9829-2a1fcacb77b5" ... )
- async create_schedule(*, name, params, schedule, actions=None, enabled=None, space_id=None, validate_spaces=None)[source]¶
Create an Attack Discovery schedule.
Creates a schedule that periodically generates attack discoveries (
POST /api/attack_discovery/schedules). A schedule is backed by an alerting rule of typeattack-discovery.- Parameters:
name (str) – The name of the schedule.
params (dict[str, Any]) – The schedule configuration parameters. Required keys:
alerts_index_pattern(index pattern to get alerts from),api_config(LLM configuration withconnectorId,actionTypeIdand the connectorname) andsize(max number of alerts). Optional:query,filters,combined_filter,start,end.schedule (dict[str, Any]) – The schedule interval, e.g.
{"interval": "24h"}.actions (list[dict[str, Any]] | None) – Optional schedule actions (connector notifications).
enabled (bool | None) – Whether the schedule is enabled (server default False).
space_id (str | None) – Optional space ID to create the schedule in.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse with the created schedule (
id,name,created_by,updated_by,created_at,updated_at,enabled,params,schedule,actions).- Raises:
BadRequestError – If the schedule properties are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> created = await client.attack_discovery.create_schedule( ... name="Daily attack discovery", ... params={ ... "alerts_index_pattern": ".alerts-security.alerts-default", ... "api_config": { ... "connectorId": "my-connector-id", ... "actionTypeId": ".gen-ai", ... "name": "My GenAI connector", ... }, ... "size": 100, ... }, ... schedule={"interval": "24h"}, ... ) >>> schedule_id = created.body["id"]
- async get_schedule(*, id, space_id=None, validate_spaces=None)[source]¶
Get an Attack Discovery schedule by ID.
Retrieves a single schedule (
GET /api/attack_discovery/schedules/{id}).- Parameters:
- Returns:
ObjectApiResponse with the schedule details (
id,name,enabled,params,schedule,actions, audit fields).- Raises:
NotFoundError – If the schedule does not exist.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> schedule = await client.attack_discovery.get_schedule(id="b0ab787f-...") >>> print(schedule.body["name"])
- async update_schedule(*, id, name, params, schedule, actions, space_id=None, validate_spaces=None)[source]¶
Update an Attack Discovery schedule.
Replaces the schedule’s properties (
PUT /api/attack_discovery/schedules/{id}). All properties are required by the 9.4 API (this is a full update, not a patch).- Parameters:
id (str) – The identifier of the schedule to update.
name (str) – The name of the schedule.
params (dict[str, Any]) – The schedule configuration parameters (see
create_schedule()for the required keys).schedule (dict[str, Any]) – The schedule interval, e.g.
{"interval": "12h"}.actions (list[dict[str, Any]]) – The schedule actions. Pass
[]to keep no actions.space_id (str | None) – Optional space ID the schedule belongs to.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse with the updated schedule details.
- Raises:
NotFoundError – If the schedule does not exist.
BadRequestError – If the schedule properties are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> updated = await client.attack_discovery.update_schedule( ... id="b0ab787f-...", ... name="Renamed schedule", ... params={ ... "alerts_index_pattern": ".alerts-security.alerts-default", ... "api_config": { ... "connectorId": "my-connector-id", ... "actionTypeId": ".gen-ai", ... "name": "My GenAI connector", ... }, ... "size": 50, ... }, ... schedule={"interval": "12h"}, ... actions=[], ... )
- async delete_schedule(*, id, space_id=None, validate_spaces=None)[source]¶
Delete an Attack Discovery schedule.
Deletes the schedule and its backing alerting rule (
DELETE /api/attack_discovery/schedules/{id}).- Parameters:
- Returns:
ObjectApiResponse with the deleted schedule’s
id.- Raises:
NotFoundError – If the schedule does not exist.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> await client.attack_discovery.delete_schedule(id="b0ab787f-...")
- async enable_schedule(*, id, space_id=None, validate_spaces=None)[source]¶
Enable an Attack Discovery schedule.
Starts periodic attack discovery generation for the schedule (
POST /api/attack_discovery/schedules/{id}/_enable).- Parameters:
- Returns:
ObjectApiResponse with the schedule’s
id.- Raises:
NotFoundError – If the schedule does not exist.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> await client.attack_discovery.enable_schedule(id="b0ab787f-...")
- async disable_schedule(*, id, space_id=None, validate_spaces=None)[source]¶
Disable an Attack Discovery schedule.
Stops periodic attack discovery generation for the schedule (
POST /api/attack_discovery/schedules/{id}/_disable).- Parameters:
- Returns:
ObjectApiResponse with the schedule’s
id.- Raises:
NotFoundError – If the schedule does not exist.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> await client.attack_discovery.disable_schedule(id="b0ab787f-...")
- async find_schedules(*, page=None, per_page=None, sort_direction=None, sort_field=None, space_id=None, validate_spaces=None)[source]¶
Find Attack Discovery schedules.
Lists schedules with pagination and sorting (
GET /api/attack_discovery/schedules/_find).Note
On a live 9.4.3 server the
pageparameter is off by one: the server treats an explicitpage=Nas pageN + 1(page=1returns the second page). Omitpage(or passpage=0) to get the first page. Also, in spaces where thesecuritySolutionAttackDiscoveryfeature is disabled (e.g. spaces with the Elasticsearch solution view), this endpoint returnstotal: 0even though schedules exist.- Parameters:
page (int | None) – Page number (see the note above about the live off-by-one behavior).
per_page (int | None) – Number of schedules per page (server default 10).
sort_direction (str | None) – Sort direction,
"asc"or"desc"(server default"asc").sort_field (str | None) – Field to sort by. Common fields:
"name","created_at","updated_at","enabled".space_id (str | None) – Optional space ID to search in.
validate_spaces (bool | None) – Override space validation setting for this operation.
- Returns:
ObjectApiResponse with
page,per_page,totalanddata(the list of schedules).- Raises:
BadRequestError – If the query parameters are invalid.
AuthenticationException – If authentication fails.
AuthorizationException – If insufficient privileges.
SpaceNotFoundError – If the space doesn’t exist and validation is enabled.
- Return type:
Example
>>> found = await client.attack_discovery.find_schedules(per_page=100) >>> for s in found.body["data"]: ... print(s["id"], s["name"], s["enabled"])