Type to search.

Triage alerts with an AI agent

Serverless

Elastic Cloud Serverless projects are autoscaled environments, fully managed by Elastic and available on Elastic Cloud.

Availability

  • Generally available

    This functionality is generally available and ready for production usage.

Serverless interfaces and procedures might differ from classic Elastic Stack deployments.

Stack 9.4+

The Elastic Stack includes Elastic's core products such as Elasticsearch, Kibana, Logstash, and Beats.

Availability

  • Generally available since 9.4

    This functionality is generally available and ready for production usage.

Unless stated otherwise on the page, this functionality is available when your Elastic Stack is deployed on Elastic Cloud Hosted, Elastic Cloud Enterprise, Elastic Cloud on Kubernetes, and self-managed environments.

This documentation corresponds to the latest patch available for each minor version. If you're not using the latest patch, check the release notes for changes.

This guide walks through building a workflow that triages Attack Discovery alerts with an Elastic Agent Builder agent. The workflow creates a case for each discovery, has an agent produce a remediation analysis, posts a concise summary to Slack, and isolates the host pending review.

The workflow is adapted from ad-automated-triaging.yaml in the elastic/workflows library.

If you're new to workflows, complete Build your first workflow first.

  • Permissions. All on Analytics → Workflows, Security → Cases, and whatever Agent Builder privilege is required to invoke agents in your space. Refer to Kibana privileges.
  • Attack Discovery enabled. Attack Discovery must be running in your Elastic Security deployment and producing findings. Refer to Attack Discovery.
  • Agent Builder agent. A configured agent in Elastic Agent Builder that can reason over security context. Use one of the built-in agents (for example, the Elastic AI Agent) or a custom agent. The examples use elastic-ai-agent as a default. Substitute your agent ID.
  • Slack connector. A Slack connector or webhook URL for notifications.
  • Attach the workflow to a rule. After saving the workflow, attach it to the Attack Discovery detection rule or the rule group you want to triage. Refer to Alert triggers.

The workflow runs whenever the attached rule fires. Because Attack Discovery alerts batch multiple related alerts into a single finding, the workflow iterates with foreach over event.alerts and handles each discovery independently:

  1. Create a case populated with the discovery's attack summary, affected entities, and MITRE tactics.
  2. Attach the component alerts to the case so investigators can pivot.
  3. Call an agent to produce a remediation and response analysis. Attach the analysis to the case.
  4. Call an agent again to produce a short Slack-ready summary.
  5. Isolate the host pending review.
  6. Notify Slack with the summary, risk score, and links back to the case, host, and workflow execution.
  1. Trigger on Attack Discovery alerts

    YAML
    triggers:
      - type: alert
        enabled: true
    

    Attach the workflow to the Attack Discovery rule after saving so the rule invokes this workflow.

  2. Iterate over each discovery

    An Attack Discovery alert bundles multiple related findings. Loop over them so each discovery gets its own case:

    YAML
    steps:
      - name: for_each_discovery
        type: foreach
        foreach: event.alerts
        steps:
          # Per-discovery steps go here. Use foreach.item to access the current discovery.
    

    Inside the loop, foreach.item is the current discovery object, including foreach.item.attack_discovery.title_with_replacements, foreach.item.attack_discovery.alert_ids, and foreach.item.risk_score.

  3. Open a case from the discovery

    Populate the case body from the discovery's Markdown-formatted summary fields:

    YAML
    - name: create_case
      type: cases.createCase
      with:
        title: "[Attack Discovery] {{ foreach.item.attack_discovery.title_with_replacements }}"
        description: |
          ## Attack summary
          {{ foreach.item.attack_discovery.summary_markdown_with_replacements }}
    
          ## Detailed analysis
          {{ foreach.item.attack_discovery.details_markdown_with_replacements }}
    
          ## Affected entities
          {{ foreach.item.attack_discovery.entity_summary_markdown_with_replacements }}
    
          ## Investigation context
          - Discovery ID: {{ foreach.item.uuid }}
          - Risk score: {{ foreach.item.risk_score }}
          - Alert count: {{ foreach.item.attack_discovery.alerts_context_count }}
        owner: "securitySolution"
        severity: "high"
        tags: ["auto-triage", "attack-discovery"]
    
  4. Attach the component alerts

    Loop over the alert IDs that make up the discovery and attach them:

    YAML
    - name: attach_alerts
      type: foreach
      foreach: foreach.item.attack_discovery.alert_ids
      steps:
        - name: add_alert
          type: cases.addAlerts
          with:
            case_id: "{{ steps.create_case.output.case.id }}"
            alerts:
              - alertId: "{{ foreach.item }}"
                index: ".alerts-security.alerts-default"
                rule:
                  id: "{{ event.alerts[0].kibana.alert.rule.uuid }}"
                  name: "{{ event.alerts[0].kibana.alert.rule.name }}"
    
  5. Ask an agent for a remediation plan

    Call the agent with the discovery context and a specific prompt. The agent returns its analysis on steps.triage.output.message:

    YAML
    - name: triage
      type: ai.agent
      agent-id: elastic-ai-agent
      create-conversation: false
      with:
        message: |
          How should we remediate this attack?
    
          - Use your knowledge of Elastic Defend to generate remediation commands.
          - Include a confidence score for the true-positive ratio.
          - Do not render videos or gifs.
          - Do not include citations.
    
          <detected_attack>
          {{ foreach.item | json(2) }}
          </detected_attack>
    

    Then attach the agent's analysis to the case:

    YAML
    - name: add_analysis
      type: cases.addComment
      with:
        case_id: "{{ steps.create_case.output.case.id }}"
        comment: "{{ steps.triage.output.message }}"
    
  6. Ask an agent for a Slack-ready summary

    Reuse the agent with a different prompt to get a one-to-two-sentence summary suitable for Slack:

    YAML
    - name: ai_summary
      type: ai.agent
      agent-id: elastic-ai-agent
      create-conversation: false
      with:
        message: |
          Produce a one-to-two-sentence summary of the attack below for a Slack
          notification. Wrap entity names like hostnames in backticks.
          Output only the summary, no preamble.
    
          <detected_attack>
          {{ foreach.item | json(2) }}
          </detected_attack>
    
  7. Isolate the affected host

    Use kibana.request to call the endpoint isolation API and link the action to the case:

    YAML
    - name: isolate_host
      type: kibana.request
      with:
        method: POST
        path: /api/endpoint/action/isolate
        body:
          endpoint_ids:
            - "{{ foreach.item.host.id }}"
          comment: "Automated isolation pending analyst review. Case {{ steps.create_case.output.case.id }}."
          case_ids:
            - "{{ steps.create_case.output.case.id }}"
    

    If your Attack Discovery payload doesn't carry host.id, swap in a search step to look up the endpoint by hostname before isolation.

  8. Notify Slack

    Post a rich Slack message with the AI summary, the risk score, and deep links back to the workflow artifacts:

    YAML
    - name: notify_slack
      type: http
      with:
        url: "{{ consts.slack_webhook }}"
        method: POST
        headers:
          content-type: application/json
        body:
          blocks:
            - type: header
              text:
                type: plain_text
                text: "Attack Discovery: {{ foreach.item.attack_discovery.title_with_replacements }}"
            - type: section
              text:
                type: mrkdwn
                text: "{{ steps.ai_summary.output.message }}"
            - type: actions
              elements:
                - type: button
                  text: { type: plain_text, text: "View case" }
                  url: "{{ kibanaUrl }}/app/security/cases/{{ steps.create_case.output.case.id }}"
                - type: button
                  text: { type: plain_text, text: "View workflow run" }
                  url: "{{ kibanaUrl }}/app/workflows/{{ workflow.id }}?executionId={{ execution.id }}&tab=executions"
        timeout: 30s
    
Full workflow YAML
YAML
name: security--ai-driven-triage
description: Triage Attack Discovery alerts with an AI agent. Open a case, attach analysis, isolate the host, and notify Slack.
enabled: true
tags: ["auto-triage", "attack-discovery"]

triggers:
  - type: alert
    enabled: true

consts:
  slack_webhook: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

steps:
  - name: for_each_discovery
    type: foreach
    foreach: event.alerts
    steps:
      - name: create_case
        type: cases.createCase
        with:
          title: "[Attack Discovery] {{ foreach.item.attack_discovery.title_with_replacements }}"
          description: |
            ## Attack summary
            {{ foreach.item.attack_discovery.summary_markdown_with_replacements }}

            ## Detailed analysis
            {{ foreach.item.attack_discovery.details_markdown_with_replacements }}

            ## Affected entities
            {{ foreach.item.attack_discovery.entity_summary_markdown_with_replacements }}

            ## Investigation context
            - Discovery ID: {{ foreach.item.uuid }}
            - Risk score: {{ foreach.item.risk_score }}
            - Alert count: {{ foreach.item.attack_discovery.alerts_context_count }}
          owner: "securitySolution"
          severity: "high"
          tags: ["auto-triage", "attack-discovery"]

      - name: attach_alerts
        type: foreach
        foreach: foreach.item.attack_discovery.alert_ids
        steps:
          - name: add_alert
            type: cases.addAlerts
            with:
              case_id: "{{ steps.create_case.output.case.id }}"
              alerts:
                - alertId: "{{ foreach.item }}"
                  index: ".alerts-security.alerts-default"
                  rule:
                    id: "{{ event.alerts[0].kibana.alert.rule.uuid }}"
                    name: "{{ event.alerts[0].kibana.alert.rule.name }}"

      - name: triage
        type: ai.agent
        agent-id: elastic-ai-agent
        create-conversation: false
        with:
          message: |
            How should we remediate this attack? Reference Elastic Defend
            remediation commands, include a confidence score, and do not
            include citations or media.

            <detected_attack>
            {{ foreach.item | json(2) }}
            </detected_attack>

      - name: add_analysis
        type: cases.addComment
        with:
          case_id: "{{ steps.create_case.output.case.id }}"
          comment: "{{ steps.triage.output.message }}"

      - name: ai_summary
        type: ai.agent
        agent-id: elastic-ai-agent
        create-conversation: false
        with:
          message: |
            Produce a one-to-two-sentence summary of the attack below for a
            Slack notification. Wrap hostnames in backticks. Output only the
            summary.

            <detected_attack>
            {{ foreach.item | json(2) }}
            </detected_attack>

      - name: isolate_host
        type: kibana.request
        with:
          method: POST
          path: /api/endpoint/action/isolate
          body:
            endpoint_ids:
              - "{{ foreach.item.host.id }}"
            comment: "Automated isolation pending analyst review. Case {{ steps.create_case.output.case.id }}."
            case_ids:
              - "{{ steps.create_case.output.case.id }}"

      - name: notify_slack
        type: http
        with:
          url: "{{ consts.slack_webhook }}"
          method: POST
          headers:
            content-type: application/json
          body:
            blocks:
              - type: header
                text:
                  type: plain_text
                  text: "Attack Discovery: {{ foreach.item.attack_discovery.title_with_replacements }}"
              - type: section
                text:
                  type: mrkdwn
                  text: "{{ steps.ai_summary.output.message }}"
              - type: actions
                elements:
                  - type: button
                    text: { type: plain_text, text: "View case" }
                    url: "{{ kibanaUrl }}/app/security/cases/{{ steps.create_case.output.case.id }}"
          timeout: 30s
  • Gate the isolation on a confidence threshold. Wrap isolate_host in an if step that reads the confidence score from the agent's analysis and only isolates above a threshold.
  • Route by MITRE tactic. Use a switch step on foreach.item.attack_discovery.mitre_attack_tactics to run different remediation branches per tactic.
  • Compose a shared enrichment workflow. Extract the "enrich + analyze" sequence into a child workflow and call it from multiple parent workflows that need similar analysis.
  • Use ai.summarize instead. If you don't need agent reasoning, replace the second ai.agent call with ai.summarize for a simpler, cheaper summary.