Wazuh
XplicitTrust provides a custom decoder and ruleset for Wazuh.
The decoder extracts every field of the JSON message body into data.*. The rules generate alerts per event type, with compliance tags for PCI DSS, GDPR, HIPAA and NIST 800-53 where applicable.
Decoder and Rules
Copy the files to /var/ossec/etc/decoders/ and /var/ossec/etc/rules/ respectively, then restart the Wazuh manager.
Wazuh does not accept RFC 5425 syslog over TLS directly. Terminate TLS in front of the manager (e.g. stunnel) and relay the messages with rsyslog or syslog-ng to the manager's syslog input. Use the default Octet counting (RFC 5425) framing on the forwarder.
Original Event Time
Wazuh sets an alert's timestamp to the moment the event was processed by the analysis engine, not the time the event occurred. A relay in front of the manager (rsyslog forwarding by default) also downgrades the message to RFC 3164, which drops the year and the time zone from the syslog header. Under normal operation the difference is a few seconds. After a forwarder outage, however, the event forwarder replays the entire backlog on recovery, and all replayed alerts then show the recovery time -- even though each message carries the original event time in the data.created_at field.
To index alerts at their original event time instead, override the alert timestamp in the Filebeat ingest pipeline on the Wazuh manager:
-
Edit
/usr/share/filebeat/module/wazuh/alerts/ingest/pipeline.jsonand insert this processor into theprocessorsarray, immediately before the existingdateprocessor. It has to come after thejsonprocessor at the top of the array, which is what unpacks the alert and makesdata.created_atavailable:{ "set": { "if": "ctx.data?.created_at != null", "field": "timestamp", "value": "{{data.created_at}}" } } -
Reload the pipeline and restart Filebeat:
filebeat setup --pipelines systemctl restart filebeat -
Verify the pipeline before relying on it. Its failure handler drops any alert it cannot process, so a mistake shows up as missing alerts rather than as an error message. Simulate the pipeline with a document that carries
data.created_at; the pipeline name isfilebeat-<Filebeat version>-wazuh-alerts-pipeline:curl -sk -u admin:<indexer password> -H 'Content-Type: application/json' \ 'https://127.0.0.1:9200/_ingest/pipeline/filebeat-7.10.2-wazuh-alerts-pipeline/_simulate?verbose=true' \ -d '{"docs":[{"_source":{"message":"{\"timestamp\":\"2026-10-15T17:38:52.953+0200\",\"data\":{\"created_at\":\"2026-10-15T17:38:47.989Z\"}}","fields":{"index_prefix":"wazuh-alerts-4.x-"}}}]}'Every processor must report
"status" : "success". After thesetstep the document'stimestampmust equal thecreated_atvalue, and thedate_index_namestep must show an_indexwith the event's date (2026.10.15in this example), not the processing date.
Since both the date processor (which sets @timestamp) and the date_index_name processor (which selects the daily index) read the timestamp field, alerts are indexed at their true event time and land in the correct daily indices. The millisecond precision of created_at is what makes this work with the stock pipeline: date_index_name expects yyyy-MM-dd'T'HH:mm:ss.SSSXX by default and would drop a timestamp without a fractional part.
Notes:
- The override only applies to alerts that carry
data.created_at-- XplicitTrust events. Alerts from other sources are unaffected. - Alerts indexed before the change keep their processing time; only new alerts get the event time.
- The on-disk archives (
/var/ossec/logs/alerts/alerts.json) still record the processing time; only the indexed documents change. - The processing time is not kept in the indexed document once overridden -- if you need both times, leave the pipeline unmodified and query
data.created_atdirectly instead. - Delivery is at-least-once: after an outage an event can arrive twice. Two alerts with the same
data.event_idare the same event.
Rule Reference
The ruleset alerts on the event types below. Every other XplicitTrust event, for example one of a type added after you installed the rules, is still stored as an alert of the base rule 100100 at level 3, described by its class and type, so nothing the forwarder delivers is dropped. Update the rules to give a new type its own level and description.
Event types are matched exactly, so web_app_login does not also match web_app_login_denied.
Rules 100128, 100138 and 100139 refine the compliance change rules: a change that switches a protection off or degrades it gets the higher level, any other change of the same type keeps the rule above it. The correlation rules count events per user, device or source address, never across the whole tenant, so unrelated events of different people do not add up.
| Rule ID | Level | Event | Description |
|---|---|---|---|
| 100100 | 3 | (base) | Every XplicitTrust event without a rule of its own |
| 100101 | 3 | logged_in |
User or client login |
| 100102 | 3 | logged_out |
User or client logout |
| 100103 | 10 | failed |
Authentication failed |
| 100104 | 10 | failed_refresh |
Session refresh failed |
| 100105 | 12 | access_denied |
Access denied by policy |
| 100106 | 3 | pre_auth_created |
Pre-authentication session created |
| 100107 | 3 | session_refresh |
Session refreshed |
| 100108 | 3 | tenant_entered |
Partner user entered tenant |
| 100109 | 3 | tenant_left |
Partner user left tenant |
| 100110 | 5 | created |
Configuration object created |
| 100111 | 5 | updated |
Configuration object updated |
| 100112 | 7 | deleted |
Configuration object deleted |
| 100113 | 5 | enabled |
Configuration object enabled |
| 100114 | 7 | disabled |
Configuration object disabled |
| 100115 | 3 | fetched |
User groups fetched from IdP |
| 100116 | 5 | imported |
User groups imported from IdP |
| 100117 | 3 | synced |
User groups synced with IdP |
| 100118 | 3 | domains_fetched |
Domains fetched from IdP (also the older spelling domains fetched) |
| 100119 | 3 | configuration_downloaded |
Asset configuration downloaded (also configuration downloaded) |
| 100120 | 3 | client_online |
Client came online |
| 100121 | 3 | client_offline |
Client went offline |
| 100122 | 3 | asset_online |
Asset came online |
| 100123 | 5 | asset_offline |
Asset went offline |
| 100124 | 5 | public_ip_changed |
Client public IP changed |
| 100125 | 10 | ip_blocked |
Login blocked by IP allowlist |
| 100126 | 10 | geo_blocked |
Login blocked by country restriction |
| 100127 | 10 | session_ip_mismatch |
Session blocked after IP address change |
| 100128 | 10 | security_product_changed |
A security product is now off, snoozed or expired |
| 100130 | 7 | os_version_changed |
Client OS version changed |
| 100131 | 7 | security_product_changed |
Security product changed |
| 100132 | 7 | disk_encryption_changed |
Disk encryption state changed |
| 100133 | 7 | geolocation_changed |
Client geolocation changed |
| 100134 | 5 | client_version_changed |
Client version changed |
| 100135 | 7 | security_provider_changed |
Security provider changed |
| 100136 | 7 | domain_membership_changed |
Domain membership changed |
| 100137 | 7 | entra_id_membership_changed |
Entra ID membership changed |
| 100138 | 12 | disk_encryption_changed |
Disk encryption turned off |
| 100139 | 10 | security_provider_changed |
Antivirus or firewall health degraded to poor |
| 100140 | 12 | (correlation) | Brute force: 5+ failed logins for the same account in 2 minutes |
| 100141 | 12 | (correlation) | 5+ Web App login denials for the same user in 10 minutes |
| 100142 | 12 | (correlation) | The same user logged in from two countries within an hour |
| 100143 | 13 | (correlation) | The same device denied access 5+ times in 10 minutes |
| 100144 | 12 | (correlation) | 5+ logins blocked by IP allowlist or country restriction from the same address in 10 minutes |
| 100145 | 12 | (correlation) | The same user deleted 10+ objects in 5 minutes |
| 100146 | 12 | (correlation) | 10+ failed logins in 2 minutes across accounts (password spraying, or a failing identity provider) |
| 100147 | 12 | (correlation) | Brute force: 5+ failed logins from the same address in 2 minutes |
| 100150 | 5 | asset_isolation_enabled |
Asset isolation enabled |
| 100151 | 10 | asset_isolation_disabled |
Asset isolation disabled |
| 100152 | 5 | asset_isolation_rule_added |
Isolation rule added |
| 100153 | 5 | asset_isolation_rule_changed |
Isolation rule changed |
| 100154 | 7 | asset_isolation_rule_removed |
Isolation rule removed |
| 100160 | 5 | new_host |
New host on a monitored network |
| 100161 | 5 | new_service |
New service on a monitored host |
| 100162 | 3 | host_disappeared |
Monitored host disappeared |
| 100163 | 3 | service_closed |
Monitored service closed |
| 100164 | 3 | scan_completed |
Network scan completed |
| 100170 | 7 | cpu_usage_high |
Client CPU usage high |
| 100171 | 3 | cpu_usage_normal |
Client CPU usage back to normal |
| 100172 | 7 | memory_usage_high |
Client memory usage high |
| 100173 | 3 | memory_usage_normal |
Client memory usage back to normal |
| 100180 | 5 | notification.* |
Notification raised in the console |
| 100181 | 3 | notification_cleared.* |
Notification resolved |
| 100182 | 7 | notification.broker_failed |
Broker unreachable |
| 100183 | 7 | notification.split_dns_failed |
Split DNS broken |
| 100184 | 7 | notification.dhcp_failed |
DHCP failed |
| 100185 | 7 | notification.event_forwarder_failed |
Event forwarder failing |
| 100190 | 3 | web_app_login_started |
Web App login started |
| 100191 | 3 | web_app_login |
Web App login |
| 100192 | 10 | web_app_login_denied |
Web App login denied, with the reason |
| 100193 | 7 | web_app_sign_out_all |
Everyone signed out of a Web App |
| 100194 | 3 | upstream_firewall.credentials_delivered |
The agent fetched the firewall credentials |
| 100195 | 5 | upstream_firewall.credentials_handed_over |
Firewall credentials handed over to the agent |
| 100196 | 3 | upstream_firewall.credentials_verified |
The firewall accepted the agent's credentials |
| 100197 | 7 | upstream_firewall.credentials_expired |
Credentials expired before the agent fetched them |
| 100198 | 10 | upstream_firewall.credentials_invalid |
The firewall rejected the agent's credentials |
| 100199 | 10 | upstream_firewall.sync_failed |
Firewall sync failed |
| 100200 | 3 | upstream_firewall.sync_recovered |
Firewall sync recovered |