Inspiration

Security operations teams are drowning in alerts, and AI can help triage them — but AI hallucinations in a security context are uniquely dangerous. A fabricated timestamp or wrong host attribution sends analysts to the wrong machine while the real attacker pivots. The question isn't just can AI investigate an incident? It's can you trust what it found?

The core insight behind this project: don't just use AI to investigate. Use a second AI to check the first one.

What it does

Elastic IR Agent is an autonomous incident response agent that:

  1. Investigates — A Triage Agent (Gemini 2.5 Flash) calls six ES|QL detection tools through the Elastic Agent Builder MCP server, reconstructing the attack chain, mapping MITRE ATT&CK techniques, and identifying IOCs across 73,909 real Windows attack events.

  2. Verifies — A Forensic Auditor (a second, independent Gemini instance with no shared context) re-queries Elasticsearch with read-only tools and labels every claim VERIFIED, REFUTED, or UNVERIFIABLE — citing the exact ES|QL output that decided it.

  3. Acts — After verification, the agent writes a structured containment runbook back to ir-agent-memory: isolated hosts, credentials to reset, processes to remove — keyed by session ID so it's queryable later.

  4. Remembers — Findings are stored in Elasticsearch using ELSER sparse-vector

    • BM25 hybrid search (RRF), scoped to the investigation session so the agent builds on what it already knows without leaking data across cases.

Refutals are findings. In one run, the Forensic Auditor confirmed the right process on the right host — but with the wrong timestamp. That catch eliminated a false positive before it reached an analyst.

How we built it

The Elastic Agent Builder MCP server exposes the ES|QL tools directly. Because Conversational Agents can't reach an authenticated private endpoint, we built a Cloud Run proxy that translates REST calls to JSON-RPC 2.0 and injects the API key server-side — making the Elastic MCP endpoint reachable by any LLM orchestration layer without exposing credentials.

The Forensic Auditor runs as a separate agent loop with a strict read-only tool allowlist. write_memory is absent from its tool declarations entirely — not blocked by a prompt instruction.

Security controls are architectural, not instructional:

  • Session isolation enforced at the Elasticsearch filter level
  • Write allowlist checked in code at write time, not at config load
  • Audit log written via os.open/os.write (atomic, not buffered IO)
  • Input validation before every ES|QL call (regex + DoS cap)

Challenges

Elastic MCP type coercion — The Elastic MCP server expects time_window as a string ("87600h"), but Conversational Agents sends integers. The Cloud Run proxy detects this and coerces the type before forwarding.

Conversational Agents UI bug — The agent's conversation start mode wouldn't save via the UI when the agent was originally created in Flow mode. Fixed by patching startPlaybook directly via the Dialogflow CX REST API.

Making the Forensic Auditor actually adversarial — Early versions of the Auditor would simply agree with the Triage Agent's claims. The system prompt was rewritten to force independent querying with a strict time-window calculation rule before any tool call, which produced genuine REFUTED verdicts.

What we learned

Adversarial multi-agent verification is a real, deployable pattern — not a research curiosity. The refutals from the Forensic Auditor aren't system failures; they're the system working. An IR report that has been challenged by an independent agent citing raw evidence is qualitatively more trustworthy than one that hasn't.

The MCP proxy pattern (Cloud Run + REST → JSON-RPC 2.0 + auth injection) is reusable for any authenticated backend that an LLM orchestrator can't reach directly. It decouples the agent from the credential management problem.

Built With

Share this project:

Updates