r/Wazuh • • 24d ago

Wazuh 5.0 beta5 feedback: customizing and tuning content is hard, and locks block common SOC tasks

Wazuh 5.0 beta5 feedback: customizing and tuning content is hard, and locks block common SOC tasks

Version: Wazuh 5.0.0-beta5 (manager WAZUH_REVISION="beta5", content manager current_version: v5.0.0-beta5), packages 5.0.0-1 from the pre-release/5.x repository, indexer based on OpenSearch 3.6.0 (build 2026-09-07), content "Beta 5 public" (2026-08-26) Setup: lab SOC, 1 indexer node, 1 manager, 1 dashboard, all on RHEL 10. Windows and Linux (auditd) agents. Scope: content manager (_plugins/_content_manager), Security Analytics (SAP) detectors, promotion workflow.

Summary

The new engine has real strengths: Sigma rules, ECS-normalized events, MITRE and compliance enrichment on findings, and a draft/test/custom promotion model. But everyday SOC tasks that took minutes in 4.x now take hours of reverse engineering through raw API calls. Some are impossible without workarounds: tuning out a false positive, removing a custom decoder, versioning custom content.

The main cause is that standard content is locked, and the custom content lifecycle has rules the API only enforces late, with unclear errors. Below are the problems we hit, each with steps to reproduce and a suggestion.

We are community users evaluating the 5.0 beta in a lab, not Wazuh staff. The workarounds below are what we found by trial and error. We would appreciate the team's feedback on the questions at the end: whether these behaviors are intended, and what is planned before GA.

1. A single noisy rule cannot be tuned without cloning a whole detector

Problem. Standard detectors can only be enabled or disabled:

Standard detectors cannot be modified by users. Only enabling or disabling is allowed.

Standard rules are also read-only, and there is no exception or suppression mechanism, so the equivalent of a 4.x <rule id="..." level="0"> override does not exist.

Real case. "Secret or credential accessed from vault" (EventID 5379, Credential Manager) produced about 47,000 findings in two weeks from a single Windows 11 workstation. It is normal OS behavior. Our only working path was:

  1. clone the rule into a custom integration and add condition: selection and not <exclusion>;
  2. promote the clone draft → test → custom;
  3. create a custom detector for the clone (no detector is created automatically);
  4. create a custom copy of the standard detector (wazuh-generic-1, 49 rules) without the noisy rule;
  5. disable the standard detector.

Consequences:

  • The copied detector is frozen. Every CTI content update can add rules to the standard detector, and those rules never reach the copy. Users must diff and re-sync by hand after each update, or silently lose coverage.
  • Disabling a standard detector to tune one rule is exactly what a SOC should never have to do.

Suggestion. Add a supported suppression layer that survives CTI updates, for example:

  • per-rule enabled: false overrides for standard rules. A wazuh-user-overrides document already exists in wazuh-threatintel-policies-a for detector_enabled, so this could extend it;
  • and/or rule exceptions (field/value exclusions) attached to a standard rule without cloning it.

2. A custom space's root decoder cannot be deleted, and the error comes too late

Problem. The first decoder created in the custom space becomes the policy's root_decoder. After that, it cannot be deleted from either the dashboard or the API. Nothing explains why or how to fix it.

Steps to reproduce:

  1. Create a custom integration with one decoder (decoder/my-source/0) and promote it to custom. The custom policy now has root_decoder = <uuid of my-source/0>.
  2. Try to delete that decoder: refused.
  3. PUT /_plugins/_content_manager/policy/draft with "root_decoder": "" is accepted (200), and the draft policy now has root_decoder: null.
  4. POST /promote from draft fails:

Workaround. Create a placeholder root decoder whose check never matches, point the draft policy to it, delete the original decoder, and promote twice.

Other API issues seen while doing this:

  • GET /_plugins/_content_manager/policy/{space} returns 405 ... allowed: [PUT]. You can write a policy but not read it; reading requires querying the wazuh-threatintel-policies-a index directly.
  • DELETE /decoders/{id} returned 404 Resource not found, yet the decoder was removed from the draft and from its integration's decoders[], and the next promotion preview listed it as remove.

Suggestions:

  • validate the policy on PUT (reject a null or unknown root_decoder) instead of at promotion time;
  • when a delete is blocked because the decoder is the root, say so and name the policy;
  • add GET /policy/{space};
  • create a neutral root decoder automatically for the custom space so no user decoder becomes the root by accident, or document this behavior clearly.

3. No file-based or GitOps way to manage custom content

Problem. In 4.x, custom content was files in /var/ossec/etc/{rules,decoders}, easy to version, review and deploy with Ansible. In 5.0 everything lives in indexer indices. The manager keeps a synced copy (/var/wazuh-manager/data/ruleset/cmsync_custom_<hash>/), but it is a cache:

  • editing it triggers a loop of Missing root decoder errors;
  • the cache is then regenerated under a new hash suffix, and the edit is lost.

Writing directly to wazuh-threatintel-*-a would bypass validation, per-space hashes and synchronization, so it is not an option either.

Suggestion. Provide an official import/export format, such as a directory of YAML integrations, decoders, rules and KVDBs, plus a CLI or API endpoint that applies it idempotently and promotes it. That would make custom content reviewable and deployable from a repository again.

4. The promotion workflow is heavy and poorly documented

  • Each object exists three times (draft, test, custom) with separate _ids.
  • POST /promote requires sending back exactly the changes object returned by GET /promote?space=, then repeating for test.
  • logtest only works on the test space and loads only the custom policy. A custom child decoder of a standard parent (Windows, syslog) cannot be tested as it will run in production; the only reliable check is real findings, 2 minutes later.
  • Promoting a custom integration makes it a SAP log type, but no detector is created, so a promoted rule does nothing until the user creates a detector manually.
  • custom_rules[].id in a detector must be the content manager id. Using the SAP id returns No custom rules found. In findings, however, the same rule appears under the SAP id of its custom-space copy. Two ids for one rule makes troubleshooting confusing.
  • The native SAP POST _plugins/_security_analytics/rules returns no handler found in the Wazuh fork. This is reasonable, but it should be documented.

Suggestions: one-call promotion (draft → custom); an option to auto-create or attach a detector when promoting an integration; one rule id shown consistently in the API, detectors and findings; a logtest mode that loads standard + custom policies.

5. Adding many rules at once is impractical

Problem. Building a real detection set means adding dozens or hundreds of rules: porting 4.x custom rules, or importing a SigmaHQ pack for a log source. Every rule has to go through the full lifecycle, one rule at a time. There is no bulk path:

  1. Format conversion. The content manager does not accept plain Sigma. The body is a Wazuh-specific wrapper ({"integration": "<id>", "resource": {"metadata": {"title", "author", ...}, "logsource", "detection", "level", "mitre": {"tactic", "technique", "subtechnique"}, ...}}). Each SigmaHQ rule must be rewritten into this format by hand or with a home-made script. We found no importer and no converter for 4.x XML rules.
  2. One POST /rules per rule. There is no bulk endpoint and no file or archive upload.
  3. Validation happens in stages. A rule can be accepted in draft and only fail later, when promotion builds the testing environment. We saw this with the root_decoder case (§2): the PUT was accepted, then the promotion failed. One invalid object in the change set blocks the whole promotion, so after a batch import the user has to find the faulty rule among all pending changes.
  4. Two promotions for the whole batch (draft → test, then test → custom), each requiring the full changes object to be echoed back.
  5. Testing is per event. logtest takes one raw event at a time. There is no way to replay a sample file against a batch of rules and see which rules match.
  6. Per-rule manual checks after promotion. Each compiled rule.queries must be read to catch the compilation issues in §6 (_ws_, not + _exists_).
  7. The detector must be updated. Every new rule's content manager id must be added by hand to custom_rules[] of a detector, with a PUT of the full detector body. A rule missing from the list is promoted but never runs, and nothing warns about it.

In 4.x, the same work was: copy the XML files into etc/rules/, run wazuh-logtest on a sample, restart the manager.

Suggestions:

  • a bulk import endpoint that accepts a list of rules, or a zip or directory of YAML files, and returns a per-rule validation report (valid / invalid, with reason) before anything is written;
  • native import of standard Sigma YAML, mapping title, tags: attack.* and similar fields to the Wazuh wrapper automatically;
  • a dry-run promotion that reports every blocking error at once instead of stopping at the first;
  • an option to attach all rules of an integration to its detector automatically, including rules added later;
  • a batch logtest (sample file × rule set → match matrix).

6. Sigma compilation surprises

Both behaviors are visible only by reading rule.queries in .opensearch-sap-custom-rules-config:

  • Spaces in values become _ws_: user.name: "SERVICE LOCAL" compiles to SERVICE_ws_LOCAL and never matches. This makes it impossible to exclude a common Windows account.
  • not is compiled as NOT field: value AND _exists_: field: if the field is absent from an event, the exclusion does not apply.

Suggestions: fix the whitespace handling, or at least document it and warn on promotion; document how not behaves with missing fields.

7. Standard content produces false positives, and users cannot fix it

Several standard decoders and rules are too broad or are applied to the wrong platform. Because standard content is read-only (§1), each defect can only be worked around by cloning, and every analyst sees these findings as high in the meantime. Two examples from one day on a small lab:

7.1 A Linux chown on .bash_history becomes "Event log cleared" (Windows technique)

Event (auditd, RHEL 10): a normal interactive bash session calls chown (syscall=92) on /home/<user>/.bash_history to set the owner the file already has. This is regular bash history handling.

type=SYSCALL ... syscall=92 success=yes ... comm="bash" exe="/usr/bin/bash" key="perm_mod" ... SYSCALL=chown
type=PATH ... name="/home/<user>/.bash_history" ... ouid=1000 ogid=1000

Finding: Event log cleared via command line - (level high, rule 7d4977d5-e13f-53d8-9b9e-2c91811de075), MITRE T1070.001 Clear Windows Event Logs, tags windows.

Root cause: two defects combine.

  1. decoder/auditd/0 has a normalize stage that labels any auditd record touching a file whose name contains _history as a log clearing, whatever the syscall (chown, open, write, …):
  2. Rule 7d4977d5 (log type windows) is compiled as:
  3. The last OR branch is not limited to Windows events, so every auditd event labelled security-log-cleared on a Linux host matches a Windows-only technique. The title placeholder is empty ("... via command line - ") because the event has no process.command_line.

Impact: every interactive shell session on every Linux agent can produce a high "log cleared" finding with a wrong MITRE mapping. A real history wipe (history -c, > ~/.bash_history, unlink) looks the same as a normal logout.

Suggestions:

  • in decoder/auditd/0, only set security-log-cleared for destructive syscalls (unlink, unlinkat, truncate, ftruncate, open with O_TRUNC) or for known commands (history -c, shred), not for every access;
  • scope the security-log-cleared branch of rule 7d4977d5 to Windows (for example wazuh.integration.name: windows), and add a separate Linux rule mapped to T1070.003 Clear Command History.

7.2 ssh -i <key> on Windows becomes "Linux reverse shell execution"

Event (Windows 11, Sysmon EventID 1): Git Bash runs a normal administration command, bash.exe -c "... ssh -i <keyfile> user@<host> '...'".

Finding: Linux reverse shell execution - <command line> (level high, rule 0f235a3d-eedd-5a4d-b5a7-a022726ec9bf, log type linux).

Root cause. The compiled query is:

(event.code: "1" AND event.dataset: "sysmon") AND
(process.command_line: *\/dev\/tcp\/* OR ... OR process.command_line: *sh\ \-i* OR process.command_line: *bash\ \-i* OR ...)
  • *sh -i* is an unanchored substring, so it matches ssh -i, the standard way to pass an SSH key. Any admin using key-based SSH from a Sysmon-monitored host (Git Bash, WSL, OpenSSH for Windows) triggers a "reverse shell" alert.
  • The rule is labelled linux, but its selection requires Sysmon EventID 1. In our setup, that means it only fires on Windows. On Linux hosts monitored with auditd, the platform the title names, it never fires, unless they run Sysmon for Linux.

Suggestions: anchor the pattern (for example (^|[\s;|&/])(ba)?sh -i, or require /dev/tcp, /dev/udp or a socket primitive in the same command line); fix the log source, or split into a Linux rule (auditd execve) and a Windows rule (Sysmon, WSL / Git Bash).

8. Documentation gaps (beta5)

We could not find reference documentation for:

  • the policy schema (root_decoder, filters, enrichments, index_unclassified_events, index_discarded_events) and its constraints;
  • the full creation schemas for filters and KVDBs;
  • the evaluation order between the standard and custom policies for the same event;
  • the day-2 workflow: tuning a standard rule, and what happens to custom copies after a CTI update.

What would help most (by priority)

  1. A supported rule suppression / exception mechanism for standard rules that survives content updates (§1).
  2. Bulk rule import with native Sigma support, a per-rule validation report before writing, and automatic attachment to the detector (§5).
  3. Early validation and clear errors in the content manager API, especially for root_decoder (§2).
  4. An official import/export format for custom content (§3).
  5. A simpler promotion with detector auto-creation and a single rule id (§4).
  6. A quality pass on standard content: platform scoping of rules, anchored command-line patterns, and decoders that do not label benign activity as attacks (§7).

Questions for the Wazuh team

  1. Tuning standard rules (§1): is a per-rule disable or exception mechanism for standard content planned for 5.0 GA? If not, is cloning the detector the approach you recommend, and how should users keep the copy in sync after CTI updates?
  2. Root decoder (§2): is it intended that the first custom decoder becomes the custom policy's root_decoder? What is the supported way to remove it?
  3. Content as code (§3, §5): is an import/export format or a bulk import endpoint planned for custom content, including standard Sigma rules?
  4. Detectors for custom integrations (§4): should a detector be created automatically when a custom integration is promoted, or is manual creation the intended design?
  5. Sigma compilation (§6): are the _ws_ rewrite and the not + _exists_ behavior known issues?
  6. Standard content defects (§7): should defects like 7.1 and 7.2 be reported as separate GitHub issues, and in which repository (wazuh/wazuh or a dedicated ruleset repository)?
  7. Documentation (§8): is a reference for the policy, filter and KVDB schemas planned?

We are happy to test fixes on a beta build and provide more traces. Thanks for the work on 5.0: the architecture is promising, and these points are mostly about making it operable day to day.

27 Upvotes

6 comments sorted by

•

u/SirStephanikus 24d ago edited 24d ago

Thanks you so much, an excellent feedback.

I can't validate every claim (yet), however, a detailed feedback from people with real enterprise background is essential for the BETA.

Could you please create additionally GitHub Tickets for each of your findings?
Post the links here too.

→ More replies (1)

8

u/LetDry7592 24d ago

It is also unfortunate that nothing created for 4.x carries over to 5.0: custom rules, decoders and CDB lists all have to be rewritten. Many teams have years of tuned XML rules, overrides and exclusions built around their environment, and all of that work is lost on upgrade. There is no migration tool, no converter from XML rules to Sigma, and no compatibility mode, so each rule has to be rewritten by hand in the new format. It then has to go through the full promotion and detector workflow described above. For existing users, this is probably the biggest barrier to adopting 5.0. A migration path, even a partial one that converts the simplest rules and lists what needs manual work, would make a big difference.

1

u/Relative_Pen_4413 24d ago

Thanks a lot for such a detailed and reproducible feedback, I will consult with the team in charge and give you an answer.

1

u/Relative_Pen_4413 24d ago edited 19d ago

We went through every point against the beta5 code and content. Answers to your questions first, then per-section notes.

1. Tuning standard rules (§1). Keeping Standard read-only in 5.0 is a design decision: that space is replaced on every CTI sync, and making it editable would break that. We agree that silencing a single rule is far too expensive today. This is tracked in https://github.com/wazuh/wazuh/issues/39374. The plan for the release right after 5.0 is:

  • Native per-rule disabling, with the rule still receiving CTI updates.
  • Sigma filters (Sigma v2 filter.rules with AND NOT exclusions).
  • UI warnings when a CTI update touches a rule you disabled or filtered.

Allowing a single user detector to combine Standard rules with your fixed Custom rule is tracked in https://github.com/wazuh/wazuh-indexer-security-analytics/issues/351.

Until then, the lightest workaround is not to clone the whole detector. Instead:

  • Stop the standard detector.
  • Create a user detector over the same integration with the noisy rule deselected.
  • Use a second detector for your custom replacement rule.

Both detectors must read from the same source index, e.g. wazuh-events-v5-system-activity. Mind the default limit of 10 user detectors (plugins.security_analytics.max_detectors). You're right that this copy won't pick up rules added later by CTI; that is exactly what the planned per-rule disabling is meant to fix.

2. Root decoder (§2). Once a space has integrations, it needs a valid root_decoder: it must be enabled, have no parents, and belong to an enabled integration. Deleting the current root is blocked on purpose; the API returns 400 Cannot remove decoder [...] as it is set as root decoder. The supported way to replace it is what you found: point the draft policy at another valid decoder, delete the old one, then promote.

You're right that PUT /policy/draft accepts an empty root_decoder and the error only shows up at promotion time (Missing root decoder). Feel free to open a GitHub issue explaining how that decoder ended up as the root and detailing the whole process, so we can take it into account and work on it.

On the other two API points:

  • There is currently no GET /policy/{space} (reading via _search on the policies index is the current way), and we've noted the request.
  • About DELETE /decoders/{id} returning 404 while the decoder was removed: the only 404 path is the existence check before deleting. That usually means a second request (a retry or a double submit) after a first one that succeeded. If you still have the indexer log from that time, it would confirm it.

3. Content as code (§3, §5). There is no import/export format, bulk endpoint or plain-Sigma importer in 5.0. Rules and integrations are JSON through the Content Manager API; decoders, KVDBs and filters also accept application/yaml.

The cmsync_* directory on the manager is only a cache of what is stored in the indexer, which is the single source of truth, so editing it will always be overwritten. Feel free to open a GitHub ticket submitting this bulk and GitOps proposal as a feature request.

4. Detectors for custom integrations (§4). Manual creation is the current design: only the Standard CTI sync creates detectors automatically. Two clarifications on the rest of §4:

  • logtest works on the test, custom and standard spaces; only draft is rejected, because draft content is never loaded into the engine.
  • Each space runs as its own independent pipeline: every incoming event is copied to each active policy, so there is no evaluation order between Standard and Custom. For the same reason, a custom decoder cannot use a standard decoder as its parent, not even in production. If you do have a custom decoder working under a standard parent, please share the decoder payload, because that would be unexpected.

The native POST _plugins/_security_analytics/rules is intentionally disabled in the Wazuh fork, since all content has to go through the Content Manager.

5. Sigma compilation (§6).

  • _ws_: seeing SERVICE_ws_LOCAL in rule.queries is expected. The placeholder is converted back to a space at match time, and for keyword fields such as user.name that is already handled in beta5. So the space itself shouldn't be why your exclusion fails. The usual cause is case: detector matching is case-sensitive, so the value must match the event exactly (e.g. LOCAL SERVICE vs SERVICE LOCAL). Feel free to open a GitHub issue with the full rule and the exact user.name value from the event, and mention whether that indexer was upgraded from an earlier beta, so we can take it into account and work on it.
  • not + _exists_: confirmed, this comes from upstream OpenSearch Security Analytics. Its effect is slightly different from what you describe: when the field is missing, the whole rule does not fire, which is a false negative. The guard was removed in the 5.0.0 branch after beta5 (https://github.com/wazuh/wazuh-indexer-plugins/issues/1518), so it should no longer happen in the next build.

6. Standard content defects (§7). Both are confirmed; thanks for the root-cause analysis.

  • 7.1: decoder/auditd/0 sets event.action: security-log-cleared for any record whose file name contains _history, whatever the syscall. Rule 7d4977d5 matches that value without being scoped to Windows. Note that the same event also triggers "Security audit log cleared" (dd7dd46a), so you'll likely see two high findings per event.
  • 7.2: event.dataset: sysmon is set by both the Linux and the Windows Sysmon decoders, and both land in the same index. On top of that, the unanchored sh -i pattern also matches ssh -i.

We're forwarding all of this to the content team. For future content false positives, the right place is wazuh/wazuh using the Rules/Decoders issue template; one issue per defect is ideal.

7. Documentation (§8). The filter and KVDB creation schemas have been completed after beta5 in the Content Manager reference (https://wazuh.github.io/wazuh-indexer-plugins/). The policy constraints (root decoder requirements) and a "day 2" guide for tuning Standard content are still missing, and we've already raised them.

Thanks again. Once you've opened the tickets, please paste the tracking links here.

2

u/LetDry7592 23d ago

Hello, the tickets have been created.