Skip to content

[Epic] Attack Discovery generation — workflow/skill parity with the Kibana UI #61

Description

@davethegut

Objective

When the MCP app generates an Attack Discovery, route it through the AD 2.0 workflow/skill pipeline — identical to a default UI "Generate" — instead of the legacy LangGraph path it uses today. Running Attack Discovery from the MCP app should behave the same way as running it from the Security UI, including using the skill-based generation pipeline.

Related Kibana work: AD 2.0 "Workflows Integration" PR stack (branch attack-discovery-workflows-integration/10-scheduling).

Background / current behavior

Today the MCP app triggers generation via the legacy route:

  • AttackDiscoveryClient.generate()POST /api/attack_discovery/_generate (elastic_assistant plugin, LangGraph path).
  • This bypasses the new AD 2.0 workflow pipeline and its skill usage entirely.

The Kibana UI, when the feature flag securitySolution.attackDiscoveryWorkflowsEnabled is on:

  • Posts to POST /internal/attack_discovery/_generate (discoveries plugin) → executeGenerationWorkflow() → the 3-phase managed-workflow pipeline (alert retrieval → generation → validation), with workflow_config.skill_enabled: true.

Why this is a small change

  • The MCP Kibana client already sends the headers internal routes require (kbn-xsrf, x-elastic-internal-origin: Kibana) — see src/elastic/kibana-client/create-kibana-client.ts.
  • Status polling is unchanged. Both the legacy and workflow paths write to the same Kibana event log, and GET /api/attack_discovery/generations returns workflow runs too. So AttackDiscoveryService.getGenerations() and the app's progress banner keep working with no changes.
  • The only gap is the start call: endpoint + request-body shape.

Design decisions

  • Route strategy: Prefer the internal workflow route; on 404/403 (flag off, or the API key lacks workflow privileges) automatically fall back to the legacy route.
  • Config surface (Option A): No new tool inputs. The generate-attack-discovery tool keeps its current schema (connectorName, size, start, end, filter). Internally we attach a fixed, UI-matching workflow_config.

Internal (workflow) request body

```json
{
"alerts_index_pattern": ".alerts-security.alerts-default",
"api_config": { "connector_id": "...", "action_type_id": "...", "model": "..." },
"size": 50,
"start": "now-7d",
"end": "now",
"source": "interactive",
"workflow_config": {
"alert_retrieval_mode": "custom_query",
"alert_retrieval_workflow_ids": [],
"alert_retrieval_workflows_enabled": false,
"default_retrieval_enabled": false,
"skill_enabled": true,
"validation_workflow_id": "default"
}
}
```

Note: the internal body is snake_case and does not take anonymizationFields / replacements / subAction — those belong only to the legacy fallback body.

Scope

In scope

  • AttackDiscoveryClient: add generateViaWorkflow() (internal route) + snake_case body type; keep legacy generate().
  • AttackDiscoveryService.generateAttackDiscovery(): build the UI-matching workflow body and try the workflow route, falling back to legacy on 404/403; return { execution_uuid } unchanged.
  • Unit tests for both paths (workflow happy path + fallback).
  • Live end-to-end verification against local Kibana.

Out of scope

  • Exposing workflow_config knobs as tool inputs (Option B).
  • Changes to status polling / progress banner.
  • Any Kibana-side changes; any UI changes beyond the MCP app.

Done criteria

  • MCP generate-attack-discovery triggers a workflow-based (skill_enabled) generation identical to a default UI Generate when the flag is on.
  • Falls back cleanly to legacy when the workflow route is unavailable (404/403).
  • Unit tests cover both paths.
  • Verified end-to-end against local Kibana on attack-discovery-workflows-integration/10-scheduling.

Risks / notes

  • The MCP API key must carry workflowsManagement:read+execute and ATTACK_DISCOVERY_API_ACTION_ALL+ALERTS_API_READ, else the internal route returns 403 and we fall back to legacy (acceptable, but worth surfacing in the returned status message / logs).

Work breakdown

Wave 1 — foundation

  • Route AD generation through internal workflow route with legacy fallback
    • src/elastic/client/attackDiscoveryClient.ts: add generateViaWorkflow(body)POST /internal/attack_discovery/_generate; keep legacy generate(body); add snake_case body type mirroring Kibana's PostGenerateRequestBody.
    • src/elastic/service/attackDiscoveryService.ts: build the internal body with the fixed workflow_config defaults + source: "interactive"; try workflow route, catch 404/403, fall back to legacy body. Return { execution_uuid } unchanged.
    • src/tools/attack-discovery.ts: (optional) report which path ran (workflow vs legacy) in the status message; no schema change.

Wave 2 — parallel (depend on Wave 1)

  • Unit tests: internal workflow route + legacy fallback — extend src/elastic/service/attackDiscoveryService.test.ts and src/tools/attack-discovery.test.ts to assert the internal route is called with the correct snake_case body + workflow_config defaults, and that 404/403 triggers the legacy fallback.
  • Live end-to-end verification against local Kibana (flag ON) — trigger generation from the MCP app; confirm POST /internal/attack_discovery/_generate is hit and a workflow/skill pipeline runs (event-log step events); confirm the progress banner shows in-progress → succeeded and results refresh; confirm legacy fallback when the flag is off / privileges are missing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions