Skip to contentSkip navigation

Configure the Wazuh integration

Trigger groups, the API key file, the enrichment envelope field by field, the verdict gates, and what to check when nothing appears.

Wazuh
On this page (8)

Configure the Wazuh integration Documentation

What the connector reads, what it emits, and how the verdict on an enrichment alert is decided.

Read coverage before band. Only known-clean — coverage: known-clean. In coverage, no malicious evidence. licenses the word "clean"; no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. means unknown, which is a different thing again; malicious-evidenced — coverage: malicious-evidenced. In coverage, with positive evidence of malice. and ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways. mean there is evidence, whatever the band says. whisper.explain does not return coverage at all. Full contract: Coverage — what we looked at.


The API key file, and the container case

--api-key-file points at a file containing the key and nothing else. It is needed only for the keyed modes — the on-demand CLI and the agent-activity log source. Per-alert enrichment runs without it.

On a containerised manager, skip the flag and inject WHISPER_API_KEY as a container-env secret.

If the keyed modes stop working, check the key first: an unrecognised key is not rejected, it is treated as no key at all. The key is in the console.


Choosing trigger groups and rule IDs

--group sshd is a starting point, not a recommendation. Pick the groups whose alerts actually carry external indicators — perimeter, DNS, proxy, IDS — and leave the rest alone. Every alert in a trigger group is scanned, and a group full of local-only events costs a scan and produces nothing.

Indicators are pulled from a maintained table of dotted alert-field paths. The live ones include srcip, dstip, audit.srcip, src_ip, dst_ip (Cisco FTD/ASA and Sophos decoders) and dest_ip (Suricata eve). Suricata uses dest_ip, not dst_ip — both are carried, and three plausible-looking ECS-style spellings were dropped from the table because they do not exist in Wazuh.


The loop guard

The connector injects alerts. Those alerts must never trigger the connector. The guard is why an enrichment alert cannot enrich itself into a loop — if you add a trigger group broad enough to include the whisper_enrichment group, the guard is what stops it, but the right fix is to narrow the group.


The enrichment envelope, field by field

Everything lands under data.whisper.* on a new alert. Nullable fields are stripped before send, so an absent key means "not applicable", never null.

FieldTypeWhat it is
schema_versionkeywordEnvelope version, currently 1.0
iockeywordThe indicator that was enriched
typekeywordipv4 · ipv6 · domain
knownbooleanWhisper has a real graph node for this indicator. This is not a safety signal — it means the graph knows it exists
verdictkeywordknown_good · known_bad · suspicious · unknown — derived from evidence, never a copy of the score
risk_scorefloatThe explain() score. Evidence, never the verdict
levelkeywordThe explain() level enum, verbatim
availablebooleanThe scoring backend answered. false forces verdict = unknown
advisorykeywordPresent only when there is one — e.g. allowlist-vouched
permalinkkeywordDeep link to the graph view for this indicator
graph_node_idkeywordStable node id, e.g. ipv4/185.220.101.1
coverageobjectgranularity is always present. See the limitation below
source_refobjectBack-link to the triggering alert: rule_id, alert_id, agent_id, field_path, truncated original_full_log

The verdict gates

The score is evidence, never the answer. verdict is derived from the level enum plus feed polarity plus coverage — never from the score alone, and never by parsing the human explanation string, because the enum and the explanation can disagree.

Evaluated in order, first match wins:

Conditionverdict
The backend did not answer, or the indicator was not foundunknown
A positive trust signal, and no confirmed-bad or threat evidence, and a non-severe levelknown_good
A confirmed-malicious node flag, or a severe level with at least one confirmed-bad feed categoryknown_bad
Any threat category or weak bad flag short of confirmed-badsuspicious
Found, but only trust or neutral evidence and no score bandunknown

Trust never overrides threat. The known_good gate requires no confirmed-bad or threat evidence, so an allowlisted-but-compromised host derives known_bad, not known_good.

No data is not benign. A clean result means "not listed at this granularity", not "safe" — which is why the found-but-no-evidence row resolves to unknown and never to known_good.

Listed in N feeds is not itself bad. Polarity comes from the feed's category and reliability from its weight. Gate on category and weight, not on a raw feed count.

Danger — silent-wrong. Gate a Wazuh rule on coverage, never on band alone. 140.82.121.3 returns band: LOW with coverage: malicious-evidenced — a host with positive evidence of malice that any band-only gate treats as unremarkable. The connector's own verdict derivation is built to avoid exactly this, and a rule you write on top of it can reintroduce it in one line.

What coverage carries here. coverage.granularity is always present and is what a rule can read. Nullable fields are stripped before send, so gate on the keys you see rather than on the absence of one.


Turning on the agent-activity log source

install.sh --logs opts in to the agent-activity log source, which brings your tenant's own agent DNS, egress and identity activity into Wazuh. It needs a key, because it reads data that belongs to your account.


Troubleshooting

Nothing appears at all. Check grep whisper: /var/ossec/logs/integrations.log for the invoke → api → emit sequence. No invoke means the trigger group never fired; no api means egress to graph.whisper.security is blocked; no emit means the indicator was skipped.

invoke but no indicator. The alert carried no global IP or domain. Non-global addresses are skipped by design, and an alert full of RFC1918 addresses produces nothing.

Enrichment stopped after a rule change. The field the indicator used to come from may no longer be decoded. Check which field_path the last successful enrichment recorded in source_ref.

It worked and now the verdicts changed. The verdict is a live read of the graph. Feeds are added and removed, and a host's evidence changes underneath a query you did not change. That is the intended behaviour, not a fault.