# wazuh-custom-decoder

Companion files for the lesson **Write a Custom Wazuh Decoder in YAML**:
<https://maheshtata.com/wazuh/wazuh-custom-decoder/>

Tested against: Wazuh 5.0.0-beta5 (official Docker stack, lab-verified 23 September 2026), Ubuntu 24.04.
Wazuh 5.0 is in beta. Re-check every asset against the release documentation at GA.

| File | What it is |
| --- | --- |
| `decoder-core-wazuh-message.yml` | Root decoder for the integration, as published in the Wazuh 5.0-beta detection-workflow guide. Parses the raw event as JSON into the temporary field `_tmp_json`. |
| `decoder-portal-audit-json.yml` | Child decoder for the JSON audit lines. Uses `check` plus conditional `normalize` blocks to map into the Wazuh Common Schema. |
| `decoder-portal-audit-text.yml` | Sibling decoder for the same application's plain-text lines, using `parse|event.original` with logpar expressions. Produces the same WCS fields. |
| `sample-events.log` | Six sample lines, four JSON and two plain text, for Log test. |
| `logtest-normalization.json` | Request body for `POST /_plugins/_content_manager/logtest/normalization`. |
| `create-content.sh` | Creates the integration and the three decoders through the Content Manager API. Set `AUTH=user:password` before running; the script refuses to start without it, and `INDEXER` defaults to `https://localhost:9200`. |

The matching detection rule lives in [`../wazuh-custom-rules`](../wazuh-custom-rules).

## Order of operations

1. Create the integration in the **draft** space (category `access-management`). The response is
   `{"message":"<uuid>","status":201}`; `message` is the integration id, not a sentence.
2. Create the root decoder, then the two child decoders, each linked to that integration.
   Each create returns `{"message":"<uuid>","status":201}`.
3. Attach `decoder/core-wazuh-message/0` to the integration as its root decoder, from
   **Security Analytics > Overview > Actions > Edit** in the dashboard. Child decoders do not
   run until this is done, and on beta5 this step cannot be scripted (see below).
4. Promote draft to **test**. Through the API the promote body must contain a `changes` object;
   an empty body returns `{"message":"Changes object is required","status":400}`.
5. Validate with Log test.
6. Promote test to **custom**, then create a detector.

## Content Manager API behaviour on beta5

Observed on a Wazuh 5.0.0-beta5 single-node Docker stack, 23 September 2026:

- Every collection endpoint (`integrations`, `decoders`, `rules`, `kvdbs`) is **POST-only**.
  A `GET` returns `405` with `allowed: [POST]`, so there is no listing API; keep the ids your
  automation creates.
- Decoders and rules accept a JSON body of the form `{"integration":"<id>","resource":{...}}`
  and return `201`.
- A YAML body works only if the file's `---` document marker and comment lines are stripped
  before it is indented under `resource:`. Posting a file unmodified returns
  `{"message":"Invalid request body.mapping values are not allowed here ...","status":400}`.
  `create-content.sh` strips them with `sed -e '/^---$/d' -e '/^#/d' -e 's/^/  /'`.
- A request may carry only one `Content-Type` header. Two produce a
  `content_type_header_exception`. That is why the script defines `curl_cm` and `curl_cm_yaml`
  as separate helpers instead of appending a header to a shared wrapper.
- `PUT /_plugins/_content_manager/integrations/{id}` requires the full resource including
  `enabled`, and refuses to change the decoder list:
  `{"message":"Content of 'decoders' cannot be added or removed via update. Please use the specific resource endpoints.","status":400}`.
  `GET` on the same path is `405` with `allowed: [DELETE, PUT]`. Attaching the root decoder is
  therefore a dashboard step.
- Log test runs only against the `test`, `custom` and `standard` spaces. A draft-space body
  returns `Logtest is only supported for the 'test', 'custom' and 'standard' spaces.`, and a
  `test`-space body before anything has been promoted returns
  `The 'test' environment does not exist.`

## Notes on fidelity

Every YAML key, stage name and helper in these files appears in the Wazuh 5.0-beta
documentation or in the engine reference in the `wazuh/wazuh` repository. Where the
beta documentation shows only a partial example, these files stay within it rather
than extending the syntax. The asset `id` values are UUIDv4s generated for this
repository; the Content Manager generates one when the field is omitted, and the id it
returns is the one to keep.
