I put an AI analyst in front of my Wazuh SIEM so I stop drowning in alerts (n8n + Claude)

Hey everyone,

I wanted to share a workflow that I run in my homelab stack.

Quick background so the setup makes sense. I come from a network engineering background and I run my own infrastructure end to end: self-hosted hardware, a 5 node Proxmox cluster, around 25 Linux containers, the whole stack at home. Part of keeping that safe is a Wazuh SIEM watching everything, with real-time alerting into Slack.

The problem with running your own SIEM is that it fires hundreds of alerts a day. Most are noise. Some are real. Reading every one is not happening, and tuning them all out defeats the point of running a SIEM in the first place.

So I built an n8n workflow that puts an AI analyst in front of every high-severity Wazuh alert. Webhook in, infrastructure-aware risk assessment out, into Slack within seconds. About $0.001 per alert with Claude Haiku.

A real example from the live test: I had Claude fire a brute-force SSH attack at one of my Proxmox nodes. Wazuh caught it (rule 5712, level 10). The workflow analyzed it and the Slack message correctly called it a likely false positive (internal source, zero-trust network, no external exposure), and still attached the exact shell commands to investigate.

How it works on the n8n side:

  • Webhook trigger listens for Wazuh JSON payloads.
  • Config node sets thresholds, model, and timezone. One source of truth, change it once.
  • IF node qualifies the alert and drops anything below your minimum level (default 9).
  • Extract node plus the LLM call build the prompt and call the model.
  • Format and notify posts the message to Slack. Telegram and Teams variants are documented inline in the node sticky notes.

The piece that makes it actually useful is the infrastructure context block. You tell the model what is normal in your network: what is internal, what is exposed, which hosts are which. Without that, the model just rephrases the alert back at you. With it, you get a real risk call instead of another line to read. That block lives in the Extract node and it is the part worth spending time on.

The model provider is swappable. Claude Haiku by default, but OpenAI or a local Ollama model work too if you want to keep it all on-prem.

Three things to wire up: an LLM API key, a Slack incoming webhook, and the Wazuh webhook integration in ossec.conf. Everything else is drag and drop, and every node has a sticky note explaining what it does and what to set.

Workflow JSON, a setup prompt you can paste into an AI assistant to walk through deployment (including building the context block for your own environment), and a brute-force test script are all in the repo, MIT licensed:

Full walkthrough with the live attack triggering the chain end to end:

If you run a SIEM at home or for clients and have built something similar, I would like to compare notes. Happy to cover the next one too (phishing triage, EDR enrichment) if there is interest.

2個讚

Nice build. The part I’d add before this ever pages a human is an incident receipt: Wazuh rule/id, host, context version, model verdict, recommended command, and who acknowledged it.

False-positive calls are useful, but security workflows need a record of why the AI decided not to wake someone.

1個讚

@Ahmad_Shakir

SO…there’s a lot more to this after the alert.

Long story short…I embarked on an experiment back in the spring. I wanted to see how much network infrastructure an agent could actually mange. (think agentic sys admins) so I built one Claude agent (432 lines of code) and gave it the keys to the kingdom so to speak. Full SSH into all my workstations and cluster nodes. API keys to my Unifi networking hardware, Netbird, Cloudflare…everything! It worked great (latency sucked and it burned a lot of tokens when it ran but it did work well) and it’s evolved a ton since and now I have 11 specialists with a single entry point classifier/router, each least privileged and scoped for only the domain they manage (Built on LangChain) but this Wazuh alret now fires a webhook for my IT agents to investigate and remediate. If you’re insteretsed in digging into it further I have a runbook and full deck you can check out HERE

It runs all of this from a slack app now when I need to interact with it. There’s are few things that run on cron like package updates, documentation audits and drfit detection/correction

Plus a couple of Mac minis

2個讚

Welcome @Michael_Frostbutter!

The infrastructure context block is the key insight here - most AI triage setups fail because the model sees the alert without knowing what the host is supposed to do, so it can’t tell “PAM lockout on a honeypot” from “PAM lockout on a prod database.” Baking that context in statically is practical and cheap.

One improvement worth considering: pulling the infrastructure context from an external source (a Google Sheet or a simple JSON file on your server) rather than hardcoding it in the Extract node. As your cluster grows, updating context in one place is a lot cleaner than editing the workflow every time you add a new container or change a host role.

@nguyenthieutoan

So I have a team of LangChain agents that manage my homelab infra more info HERE on that if you wanna peek

Part of that is drift audits, which includes auditing and keeping the infrastructure docs current. Your thinking is exactly right on this. I already have the agent that maintains the infra documentation in my harness…would be a big unlock to use this in this workflow…will probably utilize Postgres tbh.

Hey @nguyenthieutoan, you called it, so I built it.

That static block was already bugging me for the reason you gave: every new container or role change meant editing the workflow, and a stale map is worse than none. The model just confidently reasons about a host that is not there anymore.

So it is a live read now. The hardcoded string is gone, and a Postgres node in front of the Code node pulls the current host map at runtime (with a fallback if the DB is down, so alerts never block).

The reason it was a quick lift: I already run drift audits on the homelab, and one of them keeps my infra doc honest by reconciling it against what is actually deployed. That doc was already the source of truth. So all I added was a small sync that mirrors it into Postgres on the same weekly run. Add a host, the doc updates, the analyzer sees it. One place to change, and something already watching that place for me.

Which is your honeypot versus prod database point exactly. The model knows which is which when the alert fires, because it is reading the same map an agent keeps current. Good nudge, this one was a real upgrade.

That’s a solid upgrade - the drift audit feeding Postgres as a side effect is exactly the kind of tight loop that keeps the context reliable without extra maintenance work. One more thing worth adding now that the host map is live: store a criticality column (something like prod/dev/honeypot) and inject that into the prompt so the model can calibrate severity differently for the same alert type depending on the target.

That’s a fantastic idea! @nguyenthieutoan

The conversation here has covered flagging, incident receipts for flagged events, and context freshness. One gap worth closing is on the dismissal side.

Ahmad’s receipt pattern applies when the AI decides to page someone. The harder failure mode from a security standpoint is when the AI decides something is benign and no human ever sees it. Those dismissal decisions currently leave no record: no rule ID, no confidence score, no context version. If that call was wrong, you have no artifact to investigate with – and the model cannot tell you what it concluded yesterday, or why.

The fix maps directly onto your existing workflow structure. After the AI analyst node you already have a branch: the alert path and the benign path. The alert path writes the incident receipt. On the benign path, add a second write step. The fields are simpler than the full receipt (no recommended command), but include: incident_id (generate this at workflow start so flagged and dismissed events share the same ID space and can be joined later), Wazuh rule ID and rule level, host ID and criticality, model verdict, confidence score, context version timestamp.

With dismiss receipts in place, two things become possible that are not possible now. First, retroactive correlation: query all benign decisions on a specific host over the last 24 hours sorted by rule severity. A slow pattern that looks routine event-by-event shows up as an aggregate. Second, confidence calibration using nguyenthieutoan’s criticality column: a benign call on a honeypot at 0.85 confidence is routine; the same call on a prod database at 0.55 confidence is a candidate to review. You can build a simple daily query that surfaces low-confidence dismissals on high-criticality hosts without touching the triage workflow itself.

The receipt is what makes the dismiss decision auditable rather than invisible.