AI-assisted threat hunting with Wazuh and Claude Code
Contents
TL;DR: Stand up the Wazuh server/indexer/dashboard, enroll agents on every host
you care about, then let an AI agent (Claude Code) do the hunting: it queries the
Wazuh indexer through the dashboard’s search proxy with curl, runs aggregations
mapped to MITRE ATT&CK tactics, and triages what’s noise vs. what’s real. The agent
is fast at the mechanical part — pivoting across source IPs, users, FIM changes,
process events — so you spend your time on judgement, not query syntax.
Prerequisites
- A Wazuh deployment (server + indexer + dashboard). The all-in-one quickstart is enough to start.
- Agents installed on the hosts you want visibility into (Linux and/or Windows).
- An AI coding agent with shell access (this guide uses Claude Code) that can run
curland read JSON.
Steps
Install Wazuh. The fastest path is the all-in-one installer, which stands up the indexer, server, and dashboard on one box:
curl -sO https://packages.wazuh.com/4.x/wazuh-install.sh sudo bash ./wazuh-install.sh -aIt prints the generated
adminpassword at the end. Log into the dashboard athttps://wazuh.example.comand confirm it loads.Enroll your agents. On each host, install the agent and point it at the manager. On Linux:
WAZUH_MANAGER="wazuh.example.com" apt-get install wazuh-agent # after adding the repo sudo systemctl enable --now wazuh-agentOn Windows, install the MSI with the same
WAZUH_MANAGERvariable, then start the service. In the dashboard, Agents management should show each host asActive.Get complete telemetry (this is the real work). Default Wazuh only watches a few logs. Before hunting, widen visibility or you’ll hunt blind:
- Linux: enable
auditd+ the Wazuh audit integration (process execution), confirm FIM (syscheck) watches/etc,/root/.ssh, cron and web roots, and turn on rootcheck + SCA (CIS benchmarks). - Windows: deploy Sysmon with a good config, and collect the Security, System, PowerShell/Operational, and Defender channels. Enable command-line and PowerShell script-block logging via policy.
- Linux: enable
Hand the agent short-lived credentials via cookies. The Wazuh dashboard proxies searches to the indexer, so you don’t need to open the indexer port directly — you just need a logged-in session. Rather than share a long-lived password or API key, export the short-lived session cookies from your browser after logging in and give those to the agent. Two ways to grab them:
- Chrome DevTools:
F12→ Application → Storage → Cookies → select the dashboard origin, and copy thesecurity_authenticationvalue. - A cookie-editor extension (e.g. Cookie-Editor): open it on the dashboard
tab and export the cookies as JSON. You’ll see the Wazuh set —
security_authentication,wz-token,wz-user,currentApi,currentPattern, and friends.

The Cookie-Editor extension showing the dashboard’s session cookies —
security_authenticationis the one the search proxy checks.The one that authenticates the search proxy is
security_authentication. Because it’s tied to your login session it expires on its own — that’s the point. Hand it over, hunt, and it dies shortly after; nothing long-lived is exposed. Confirm the agent can read alerts:COOKIE='security_authentication=<your-session-cookie>' curl -sk -H "Cookie: $COOKIE" -H "osd-xsrf: true" \ -H "Content-Type: application/json" \ -X POST "https://wazuh.example.com/api/console/proxy?path=wazuh-alerts-*/_search&method=POST" \ -d '{"size":0,"track_total_hits":true}'A JSON response with a
hits.totalcount means the agent can now read your alerts. Still treat the cookie like a credential while it’s live — it grants dashboard access — but its short lifetime is exactly why cookies beat a static key here.- Chrome DevTools:
Have the agent profile the data first. Before hunting specifics, ask it to map the terrain: time range, which agents report, alert-level distribution, top rules, rule groups, and the MITRE techniques already present. This tells you where the signal is. A single aggregation does it:
{ "size": 0, "aggs": { "agents": { "terms": { "field": "agent.name", "size": 20 } }, "levels": { "terms": { "field": "rule.level", "size": 20 } }, "mitre": { "terms": { "field": "rule.mitre.id", "size": 30 } } } }Hunt tactic by tactic (MITRE ATT&CK). Point the agent at each tactic and let it pivot. The high-value queries on a fresh internet-facing deployment:
- Credential Access (T1110): aggregate failed auths by
data.srcipanddata.srcuser— brute-force sources and the usernames they spray. - Initial Access / Valid Accounts (T1078): aggregate successful auths by source IP. The key question: does any brute-force source IP ever appear in the success list? If yes → likely compromise.
- Persistence (T1136 / T1543): new user/account creation, new services, new scheduled tasks or cron jobs.
- Defense Evasion (T1562): agent-stopped events, log clearing, security-tool tampering.
- FIM (syscheck): files added/modified in sensitive paths.
- Credential Access (T1110): aggregate failed auths by
Triage: separate deployment noise from real threats. On a new install, most anomalies trace to the install window — service accounts the installer created, agent restarts, firewall config writes. The agent is good at correlating timestamps to prove “this clustered during setup.” What’s left after that is the real hunt surface. Turn confirmed detections into permanent rules so the next hunt is automatic.
Commands
A reusable query helper so the agent (or you) can fire aggregations quickly:
#!/usr/bin/env bash
# wq.sh — query wazuh-alerts-* via the dashboard console proxy; body on stdin
COOKIE='security_authentication=<your-session-cookie>'
curl -sk --max-time 40 \
-H "Cookie: $COOKIE" -H "osd-xsrf: true" -H "Content-Type: application/json" \
-X POST "https://wazuh.example.com/api/console/proxy?path=wazuh-alerts-*/_search&method=POST" \
-d @-
# Brute-force sources: who is hammering SSH, and which hosts
./wq.sh <<'EOF'
{"size":0,
"query":{"match":{"rule.groups":"authentication_failed"}},
"aggs":{"src":{"terms":{"field":"data.srcip","size":15},
"aggs":{"agents":{"terms":{"field":"agent.name","size":5}}}},
"users":{"terms":{"field":"data.srcuser","size":20}}}}
EOF
The prompts I used
The whole hunt was two prompts. The first sets up access and asks for a plan; the second turns the agent loose on the data.
Here's a Wazuh dashboard (admin) at https://wazuh.example.com and the
short-lived session cookies exported from my cookie editor: <cookies>.
List the steps a threat hunter would take to make this platform useful.
I have 3 Linux and 1 Windows agent (1 macOS upcoming).
That produced a phased roadmap (verify the plumbing → expand telemetry → enrichment → tune noise → active hunting → operationalize):

The agent’s phased roadmap — from verifying agents report, through expanding telemetry, to the active-hunting loop and operationalizing.
Then, to actually hunt:
Do the active-hunting phase for me.
The agent found the dashboard’s search proxy, wrote a small curl query helper, and
ran aggregations across MITRE ATT&CK tactics against the live alert index — no manual
query writing on my part.
What the agent found
The agent’s actual hunt report — real hostnames and IPs redacted, but the structure is the point:

The agent’s Phase-4 results — a per-tactic table, a no-compromise verdict, and the one real action item (SSH brute-force). Hostnames and source IPs are blacked out.
And the same report as sanitized text (hostnames and IPs replaced with placeholders) — note the shape of the answer, not the specifics:
Data hunted: ~2,900 alerts, ~1.5 h window (deployment + first hour of exposure).
Verdict: No evidence of compromise. One active external threat: SSH brute-force.
Hunt results by ATT&CK tactic
- Credential Access — Brute Force (T1110.001): ~400 failed auths from ~90 source
IPs against all 3 internet-facing hosts (e.g. 203.0.113.11, 203.0.113.24).
Sprayed usernames: admin, test, remote, ubuntu, deploy... → REAL, active,
but unsuccessful (internet bot noise).
- Initial Access — Valid Accounts (T1078): only successful SSH source =
198.51.100.10 (6 successes, 0 failures). No attacker IP ever succeeded. → benign
(admin host — confirm).
- Persistence — Create Account (T1136): 1 event = service account created by the
installer at deploy time. → benign.
- Defense Evasion — Agent stopped (T1562.001): all inside the install window. → benign.
- FIM (syscheck): all changes = firewall + monitoring-agent config at setup. → benign.
- Windows: logon + new service + agent install events. → benign.
- Malware (ClamAV): 1 "virus" hit = actually a DB-update log line. → false positive.
The one thing that needs action: disable SSH password auth (keys only), restrict SSH
by source IP, add fail2ban / Active Response. Confirm 198.51.100.10 is your admin host.
Coverage gaps: no auditd/execve on Linux and no Sysmon on Windows → cannot hunt
process execution yet. Only ~1.5 h of data → no baseline. Fix telemetry, then re-hunt.
The agent’s key move was cross-referencing brute-force source IPs against the successful-login source IPs. Zero overlap → no compromise, despite hundreds of scary-looking failed-login alerts. That correlation is exactly the tedious pivot an AI agent is good at.
Gotchas
- Hunting is only as deep as your telemetry. Without
auditdon Linux and Sysmon on Windows, you cannot hunt process execution — no LOLBins, no reverse shells, no command lines. Fix telemetry before blaming the hunt. - You need a baseline. A brand-new deployment has an hour of data and no sense of “normal.” Re-run the hunt after 1–2 weeks of steady-state before trusting deviation-based conclusions.
- The session cookie is a credential. Anyone with it can read your dashboard. Don’t paste it into shared logs, and rotate it (re-login) when done. For anything long-lived, use a proper Wazuh API user instead of a browser session.
- The AI does pivots, not judgement. It’s excellent at “aggregate failures by source IP and cross-reference against successes.” Whether an IP is your admin jump host or an attacker is still your call — feed it the context it can’t know.
- Lock down SSH regardless. Internet-facing hosts get continuous brute-force. Disable password auth (keys only) and restrict SSH by source; that removes the single loudest source of alerts and the underlying risk at once.