Skip to content

Repository files navigation

ThreatLens

ThreatLens

AI-assisted alert triage for Wazuh.

Every alert your Wazuh manager produces, summarized in plain language — what happened, why it matters, what to do — with a verdict, right inside the dashboard you already use.

CI License: GPL v2 Status: alpha Wazuh 4.x


The AI Summary page

Status: alpha. Built and validated end-to-end on a real Wazuh 4.9.2 single-node install (manager + indexer + dashboard, TLS and auth on, real connected agents). It has not been tested on multi-node clusters, Wazuh 5.x, or non-Debian distributions. Run it in a lab first.

Table of contents

What it does

  • Summarizes every new alert. Plain-language "what happened / why it matters / suggested action", plus a verdict (true_positive, false_positive, needs_review), a confidence score, key indicators, and the model's reasoning.
  • Lives in the dashboard you already use. An AI Summary page with severity tiles, a filterable list, a time-range picker, a detail flyout, token/cost usage, and a link back to the raw alert in Discover.
  • Prioritizes by severity. Critical alerts are summarized before high, high before medium. A burst of routine noise can never delay a critical alert sitting behind it in the queue.
  • Never slows down Wazuh. The summarizer is a separate OS process with a bounded queue and real backpressure. If it dies, or your provider goes down, normal Wazuh alerting is completely unaffected.
  • Falls back to a local model. Configure a cloud provider plus a local one (Ollama, vLLM, anything OpenAI-compatible) and it drops to local when the cloud provider hits a quota or goes unavailable.
  • Answers questions about your data. A Chat tab with 29 read-only tools across findings, vulnerabilities, FIM, SCA, MITRE, PCI DSS and agent inventory. No mutating tools exist.

What it does not do

  • No historical backfill. Only alerts arriving after the service starts are summarized. This is deliberate — otherwise your first install would send every old, already-handled alert to a paid API.
  • No redaction. Alert content, including full_log, reaches your LLM provider as-is. See Security.
  • No automated response. It reads and summarizes. It never restarts agents, blocks IPs, or changes Wazuh configuration.
  • It is not a detection engine. Verdicts are triage assistance. An analyst still decides.

Screenshots

Chat over your findings

Chat tab

Ask in plain language; get a real result table back, not just prose. Provider is switchable per conversation — the shot above is running against a local Ollama model.

Works on mobile

AI Summary on a phone

The severity tiles, usage counters and summary list all reflow, so triage from a phone during an on-call page is workable.

How it works

Wazuh manager                    ai-summarizer (systemd)              Wazuh Indexer
┌──────────────┐   tails file   ┌────────────────────┐   writes      ┌────────────────────┐
│ alerts.json  │ ──────────────>│ tail → parse →     │──────────────>│ ai-summaries-*     │
│ (real alerts)│                │ severity queue →   │  (dedicated,  │ (never touches     │
└──────────────┘                │ LLM call → write   │   least-priv  │  wazuh-alerts-*)   │
                                └─────────┬──────────┘   account)    └────────────────────┘
                                          │                                    ^
                                  calls out to                                 │ reads (as the
                                ┌─────────▼──────────┐                         │ logged-in user)
                                │ OpenAI / Gemini /  │               ┌─────────┴──────────┐
                                │ local (Ollama…)    │               │ wazuh-ai-assistant │
                                └────────────────────┘               │ dashboard plugin   │
                                          ^                          │ (AI Summary page)  │
                                          │ polls prompt config      │                    │
                                          └──────────────────────────┤                    │
                                                                     └────────────────────┘

Two things worth understanding:

  • The summarizer never touches wazuh-alerts-*. It reads the manager's own log file and only ever writes to its own ai-summaries-* index, using a dedicated account with no access to anything else.
  • The plugin and the summarizer barely talk. The one exception: when you edit a prompt on the Settings tab, the summarizer picks it up within ~30s over a small authenticated polling route.

Requirements

Wazuh 4.x, single node, standard /var/ossec layout (manager + indexer + dashboard on one host)
OS Debian or Ubuntu. The installer refuses to run elsewhere rather than failing halfway
Access root / sudo on that host
Provider An OpenAI or Gemini API key, or a reachable OpenAI-compatible endpoint (Ollama, vLLM, LiteLLM)
Network Outbound HTTPS to your provider, unless running fully local

Wazuh 5.x is not supported — its dashboard platform and React version differ, so the pre-built plugin here won't load. See Building from source.

Install

git clone https://github.com/uckix/threatlens.git
cd threatlens
sudo ./install.sh --provider gemini

It prompts for your API key with the input hidden. For automation, use a key file or the environment instead:

sudo ./install.sh --provider openai --api-key-file ./key.txt
AI_SUMMARIZER_API_KEY=sk-... sudo -E ./install.sh --provider openai --non-interactive

The installer will not accept a key as a command-line argument. Anything in argv is visible in ps to every local user and lands in your shell history.

That single command:

  1. Refuses immediately if this isn't Debian/Ubuntu, isn't Wazuh 4.x, or the Indexer isn't up.
  2. Verifies the shipped plugin zip against dist/SHA256SUMS.
  3. Verifies your API key with a real call before touching anything else.
  4. Installs Node.js if needed and builds the summarizer.
  5. Creates a least-privilege Indexer account (ai_summarizer) with write access to ai-summaries-* only — never the admin cert, never wazuh-alerts-*.
  6. Installs the summarizer as a sandboxed systemd service with its own unprivileged account, secrets in a 600 file, and log rotation.
  7. Backs up opensearch_dashboards.yml, installs the dashboard plugin, and restarts the dashboard — rolling both back automatically if it fails to come up.

If anything fails partway, the script prints exactly what was already created and how to undo it. Every step is safe to re-run.

Useful flags:

--api-key-file <path>   read the key from a file instead of prompting
--model <id>            default: gpt-4o-mini (openai) / gemini-flash-lite-latest (gemini)
--skip-plugin           install only the backend service, leave the dashboard alone
--rotate-token          on reinstall, rewrite the existing dashboard token block
--yes                   don't ask before restarting wazuh-dashboard
--non-interactive       fail instead of prompting
--wazuh-home <path>     default: /var/ossec
--indexer-certs <path>  default: /etc/wazuh-indexer/certs
--dashboard-config <p>  default: /etc/wazuh-dashboard/opensearch_dashboards.yml

Run ./install.sh --help for the full list.

Reinstalling

If the dashboard already has a ThreatLens config block, the installer stops and asks before touching it. A fresh token would silently break prompt sync with the existing one, so pass --rotate-token to keep the two in step, or run ./uninstall.sh first for a clean slate.

Verify it's working

systemctl status ai-summarizer
tail -f /var/log/ai-summarizer/ai-summarizer.log

To check the Indexer directly:

curl --cacert /etc/wazuh-indexer/certs/root-ca.pem \
     --cert /etc/wazuh-indexer/certs/admin.pem \
     --key /etc/wazuh-indexer/certs/admin-key.pem \
     "https://127.0.0.1:9200/ai-summaries-*/_search?pretty&sort=@timestamp:desc&size=5"

Nothing showing up? It only processes alerts that arrive after the service starts. Trigger something — a failed SSH login is enough — and give it a minute.

Using it

Open your Wazuh dashboard and find AI Summary under Home.

  • Overview — severity breakdown, token usage and estimated cost, pending and failed counts, and the summary list. Click any row for the full detail flyout.
  • Chat — ask open-ended questions about your alerts and agents. Needs its own provider configured under Settings first; it uses a separate encrypted credential store, independent of the summarizer's key.
  • Settings — customize the prompt, per severity if you want. Changes reach the summarizer within ~30 seconds, no restart needed. Also has a health check for the summarizer, the Indexer account and your provider — start there when something looks wrong.

Upgrading an existing install? A hard reload isn't always enough for cached JS. Use a private window or clear site data.

Configuration

Runtime config lives in /etc/ai-summarizer/summarizer.env (mode 600). Edit and systemctl restart ai-summarizer to apply.

Rotating the API key:

sudo vi /etc/ai-summarizer/summarizer.env   # edit AI_SUMMARIZER_API_KEY
sudo systemctl restart ai-summarizer

Using a local model

The installer sets up OpenAI or Gemini. To point at a local OpenAI-compatible endpoint, edit the env file:

AI_SUMMARIZER_PROVIDER=openai
AI_SUMMARIZER_BASE_URL=http://127.0.0.1:11434/v1
AI_SUMMARIZER_MODEL=llama3.1:8b
AI_SUMMARIZER_API_KEY=ollama

This is the configuration to use if alert content must not leave your network.

Cloud-to-local failover

Define multiple providers and the service falls back to a local one when a cloud provider hits a quota or goes unavailable:

AI_SUMMARIZER_PRESET_IDS=gemini,ollama-local
AI_SUMMARIZER_ACTIVE_PRESET=gemini

AI_SUMMARIZER_PRESET_GEMINI_TYPE=gemini
AI_SUMMARIZER_PRESET_GEMINI_API_KEY=AIza...
AI_SUMMARIZER_PRESET_GEMINI_MODEL=gemini-flash-lite-latest

AI_SUMMARIZER_PRESET_OLLAMA_LOCAL_TYPE=openai
AI_SUMMARIZER_PRESET_OLLAMA_LOCAL_BASE_URL=http://127.0.0.1:11434/v1
AI_SUMMARIZER_PRESET_OLLAMA_LOCAL_MODEL=llama3.1:8b
AI_SUMMARIZER_PRESET_OLLAMA_LOCAL_API_KEY=ollama

A provider whose baseUrl points at localhost is classified local automatically, and failover only ever lands on a local preset. Override with AI_SUMMARIZER_PRESET_<ID>_KIND=local|cloud.

Cost

Every alert is one LLM call. A manager producing 5,000 alerts a day makes 5,000 calls a day.

Before pointing this at anything busy:

  • Check your volume first: wc -l /var/ossec/logs/alerts/alerts.json
  • Start with a cheap model (gemini-flash-lite-latest, gpt-4o-mini) or a local one
  • Tune your Wazuh rules first — this is not a substitute for filtering out noise
  • Watch the token counters on the Overview page for the first day

There is currently no built-in spend cap. Set a budget limit on the provider's side.

Uninstall

sudo ./uninstall.sh              # keeps existing ai-summaries-* data
sudo ./uninstall.sh --purge-data # also deletes it

Removes the service, its account, the Indexer role and user, the dashboard plugin, and the config block it added.

Security

Read SECURITY.md before deploying anywhere that matters. The short version:

  • Alert content leaves your network. Full full_log goes to your provider with no redaction. Run a local model if that's a problem.
  • Prompt injection is a real surface. Someone who can write to a log can try to influence a verdict. Treat verdicts as suggestions, never as authority.
  • Least privilege by default. Own unprivileged account, Indexer user scoped to ai-summaries-*, sandboxed systemd unit, no access to the admin cert or wazuh-alerts-*.
  • The plugin zip is checksum-verified at install time and in CI.

Found a vulnerability? Please open a private advisory rather than a public issue — see SECURITY.md.

Building from source

The summarizer builds normally:

cd ai-summarizer
npm install && npm run build && npm test

Set AI_SUMMARIZER_PROVIDER=mock to exercise the whole pipeline — tailing, rotation handling, parsing, idempotency, retry, Indexer writes — with no API key and no cost.

The dashboard plugin ships pre-built as dist/wazuhAiAssistant-2.13.0.zip, because building it requires a full OpenSearch Dashboards platform checkout — a large, fragile process that hit a missing upstream source file, an OSD version mismatch, and a React 18-vs-16 incompatibility (which shipped a blank white page before being caught) during development. None of that belongs in an install script running on an arbitrary server. install.sh checks the target's actual bundled React version first and refuses rather than risk repeating that bug.

To build it yourself:

  1. Get a wazuh-dashboard checkout matching your target version.
  2. Use the Node version pinned by that checkout's .nvmrc.
  3. Copy wazuh-ai-assistant/ into wazuh-dashboard/plugins/wazuh_ai_assistant.
  4. From the wazuh-dashboard root: yarn && yarn plugin-helpers build.
  5. Regenerate the checksum: cd dist && sha256sum wazuhAiAssistant-*.zip > SHA256SUMS.

The source in wazuh-ai-assistant/ is the source the shipped zip is built from, targeting the same platform. scripts/check-artifact-drift.sh verifies that every source module has a counterpart in the artifact, and CI runs it on every push.

One known drift: the shipped zip was built before the summarizer-status route existed, and predates the AI Environment panel that reads it. Neither half is in the prebuilt artifact -- not the server route, not the UI -- so the Settings tab renders without that panel rather than showing an empty or failing one. Nothing errors. The panel appears once the plugin is rebuilt from current source. Everything else works. Rebuild per the steps above, or use --skip-plugin and build it yourself if you'd rather not install a binary at all.

Release steps, including how to keep the artifact in step, are in docs/RELEASE.md.

Repo layout

ai-summarizer/         Node/TypeScript background service (tail → queue → LLM → Indexer)
wazuh-ai-assistant/    OpenSearch Dashboards plugin source (AI Summary page, Settings, Chat)
dist/                  Pre-built plugin zip + SHA256SUMS
docs/assets/           Logo and screenshots
install.sh             One-shot installer for Wazuh 4.x on Debian/Ubuntu
uninstall.sh           Removes everything install.sh created
deploy-ai-summary.sh   Redeploys code to an already-installed host (dev convenience)
ai-summarizer.service  systemd unit

Known limitations

  • Wazuh 4.x only; 5.x needs the plugin rebuilt.
  • Single-node installs only; multi-node clusters untested.
  • Debian/Ubuntu only — the installer refuses elsewhere. RPM support wanted, see CONTRIBUTING.md.
  • No historical backfill.
  • No redaction before content reaches the provider.
  • No spend cap.
  • The shipped plugin zip predates the health-check route; rebuild it to enable that panel (see Building from source).

Contributing

Testing this somewhere that isn't the author's homelab is the single most useful thing you can do right now. See CONTRIBUTING.md.

License

GPL-2.0, matching Wazuh's own dashboard plugins, which this is designed to sit alongside.


Built by @uckix

About

AI x Wazuh

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages