auditd EXECVE/PROCTITLE arguments stay hex-encoded

40 views
Skip to first unread message

Hextra Srl

unread,
Sep 29, 2026, 8:31:37 AMSep 29
to Wazuh | Mailing List
Hi all,

Environment: Wazuh 4.14.8 (Docker single-node: manager, indexer, dashboard), Ubuntu agents reading /var/log/audit/audit.log with log_format audit.

Goal: track every command run by a service account (non-root) 

auditd rules:
-a exit,always -F arch=b64 -F auid=<user> -S execve -k user_cmd
-a exit,always -F arch=b32 -F auid=<user> -S execve -k user_cmd

and "user_cmd:command" added to etc/lists/audit-keys. Events correctly fire rule 80792.

Problem: the account works via non-interactive SSH (ssh host 'cmd'), so the full command line is a single argument of sh -c. Because it contains spaces, auditd writes it hex-encoded and the auditd decoder only extracts quoted arguments:

type=EXECVE ... argc=3 a0="sh" a1="-c" a2=6563686F207465737468785F6169636C61756465
type=PROCTITLE ... proctitle=74696D......

Result in the alert: data.audit.execve has only a0="sh" and a1="-c"; the actual command ("echo test...") is only in full_log as hex. So we can't search it in the dashboard or write rules on the command content .

Same happens for any argument with spaces, e.g. sudo echo "hello world".

Questions:
1. Is there a supported way in 4.x to decode hex EXECVE/PROCTITLE values (decoder option, analysisd, logcollector)? I found issue #5446 (open) which seems related but is about non-ASCII filenames.
2. Is this on the roadmap (e.g. Wazuh 5 / new engine)?
3. What is the recommended approach today: LAUREL + JSON decoder (issue #14461 mentions there are no rules for it), an indexer ingest pipeline to add a decoded field, or something else?
4. If LAUREL is the way, is there a reference ruleset equivalent to 0365-auditd_rules.xml for its JSON output?

Thanks!

diego...@wazuh.com

unread,
Sep 29, 2026, 9:04:08 AMSep 29
to Wazuh | Mailing List
Hextra Srl

Thank you for the detailed report, the environment description and the sample records are very helpful.

Before proposing a solution, we would like to confirm a few details so we can reproduce your scenario as closely as possible:
  1. What version are the Wazuh agents running on the Ubuntu hosts, and which Ubuntu release(s) are they? Please also share the auditd version (output of "auditctl -v").
  2. Which log_format is configured in /etc/audit/auditd.conf (RAW or ENRICHED)?
  3. Could you share one complete audit event for one of these commands, exactly as it appears in /var/log/audit/audit.log? That is, all the records with the same msg=audit(...) ID (SYSCALL, EXECVE, CWD, PATH, PROCTITLE), without truncating the hex values. Feel free to mask hostnames, usernames or IP addresses.
  4. Could you share the full alert JSON generated for that same event (from /var/ossec/logs/alerts/alerts.json inside the manager container, or the JSON view of the alert in the dashboard), including the full_log field?
  5. Have you modified 0040-auditd_decoders.xml or 0365-auditd_rules.xml, or added any custom auditd decoders or rules (local_decoder.xml, local_rules.xml, or other files under etc/decoders and etc/rules)?
  6. What is the main goal for the decoded command: being able to search it in the dashboard, writing detection rules based on its content, or both? The answer affects which approach fits best.
  7. Would installing an additional component on the agents (for example LAUREL) be acceptable in your environment, or do you prefer a solution limited to the Wazuh manager and indexer side?
  8. Are you planning to upgrade to Wazuh 5.x in the short term, or should the solution target 4.14.x?

While you gather this information, we are already looking into the issue and will get back to you with a more complete answer as soon as possible.

diego...@wazuh.com

unread,
Sep 29, 2026, 10:34:00 AMSep 29
to Wazuh | Mailing List
We reproduced your scenario in a lab with the same setup and validated three approaches for 4.14.x. Below are the answers to your questions and the steps for each option.

Lab used for the tests

- Wazuh 4.14.8 Docker single-node (manager, indexer and dashboard)
- Ubuntu 24.04 agent with Wazuh agent 4.14.8 and auditd 3.1.2, tested with both log_format = RAW and log_format = ENRICHED in /etc/audit/auditd.conf
- Your execve audit rules for a non-root account (-k user_cmd) and "user_cmd:command" in etc/lists/audit-keys
- Commands run through non-interactive SSH (ssh host 'cmd')

We got exactly the same result as you: rule 80792 fires, data.audit.execve only contains a0="bash" and a1="-c", and the command itself is only available hex-encoded in full_log.


1. Is there a supported way in 4.x to decode these values?

Not out of the box. auditd writes a value in hex whenever it contains spaces, double quotes, control characters or non-ASCII characters. In 4.x:

- The "audit" log format of the logcollector only groups all the records of the same event into a single line. It does not decode anything.
- The default decoder (0040-auditd_decoders.xml) only extracts quoted arguments (aN="..."), and it does not extract PROCTITLE.
- Decoders can extract values with regular expressions, but they cannot transform them, so there is no way to hex-decode a value in analysisd.

Issue #5446 tracks this and it is still open, feel free to add your use case there: https://github.com/wazuh/wazuh/issues/5446


2. Is this on the roadmap?

Wazuh 5.0 is still under development, so we cannot confirm yet how this case will be handled in the final release. For now, we recommend the options below for 4.14.x, and reviewing the 5.0 release notes once it is published.


3. Recommended approach

It depends on what you need:

- Writing rules on the command content: option A (manager side) or option C (LAUREL).
- Searching the command in the dashboard: option B (indexer side) or option C (LAUREL).

Options A and B are meant to be used together: A extracts the hex values into their own fields, and B decodes those fields into readable text when the alert is indexed.


Option A: extract hex-encoded arguments with a modified auditd decoder

This follows the documented procedure to modify a default decoder.

Step 1. Copy the default decoder to the custom decoders folder, inside the manager container:

  cp /var/ossec/ruleset/decoders/0040-auditd_decoders.xml /var/ossec/etc/decoders/
  chown wazuh:wazuh /var/ossec/etc/decoders/0040-auditd_decoders.xml
  chmod 660 /var/ossec/etc/decoders/0040-auditd_decoders.xml

In the single-node deployment /var/ossec/etc is stored in the wazuh_etc volume, so the file persists across container restarts.

Step 2. Edit /var/ossec/etc/decoders/0040-auditd_decoders.xml and paste the block below right after the last "auditd-syscall" decoder (the one commented as "PATH - FILE"), just before the comment that starts with "type=CONFIG_CHANGE".

Important: the new decoders must be inside the existing auditd-syscall block of this file. We first tried adding them in a separate file (for example local_decoder.xml) and they were never applied. Sibling decoders are evaluated as a contiguous chain, and decoders loaded from another file are appended after the rest of the auditd decoders (auditd-config_change, auditd-generic, and so on), so the chain never reaches them.

<!-- EXECVE / PROCTITLE: hex-encoded values (custom).
     auditd writes a value in hex when it contains spaces, double quotes,
     control characters or non-ASCII characters. -->
<decoder name="auditd-syscall">
  <parent>auditd</parent>
  <regex type="pcre2">type=EXECVE msg=audit\([^)]+\): argc=\d+.*? a0=([0-9A-F]+)(?=\s|$)</regex>
  <order>audit.execve.a0_hex</order>
</decoder>

<decoder name="auditd-syscall">
  <parent>auditd</parent>
  <regex type="pcre2">type=EXECVE msg=audit\([^)]+\): argc=\d+.*? a1=([0-9A-F]+)(?=\s|$)</regex>
  <order>audit.execve.a1_hex</order>
</decoder>

<decoder name="auditd-syscall">
  <parent>auditd</parent>
  <regex type="pcre2">type=EXECVE msg=audit\([^)]+\): argc=\d+.*? a2=([0-9A-F]+)(?=\s|$)</regex>
  <order>audit.execve.a2_hex</order>
</decoder>

<decoder name="auditd-syscall">
  <parent>auditd</parent>
  <regex type="pcre2">type=EXECVE msg=audit\([^)]+\): argc=\d+.*? a3=([0-9A-F]+)(?=\s|$)</regex>
  <order>audit.execve.a3_hex</order>
</decoder>

<decoder name="auditd-syscall">
  <parent>auditd</parent>
  <regex type="pcre2">type=EXECVE msg=audit\([^)]+\): argc=\d+.*? a4=([0-9A-F]+)(?=\s|$)</regex>
  <order>audit.execve.a4_hex</order>
</decoder>

<decoder name="auditd-syscall">
  <parent>auditd</parent>
  <regex type="pcre2">type=EXECVE msg=audit\([^)]+\): argc=\d+.*? a5=([0-9A-F]+)(?=\s|$)</regex>
  <order>audit.execve.a5_hex</order>
</decoder>

<decoder name="auditd-syscall">
  <parent>auditd</parent>
  <regex type="pcre2">type=EXECVE msg=audit\([^)]+\): argc=\d+.*? a6=([0-9A-F]+)(?=\s|$)</regex>
  <order>audit.execve.a6_hex</order>
</decoder>

<decoder name="auditd-syscall">
  <parent>auditd</parent>
  <regex type="pcre2">type=EXECVE msg=audit\([^)]+\): argc=\d+.*? a7=([0-9A-F]+)(?=\s|$)</regex>
  <order>audit.execve.a7_hex</order>
</decoder>

<decoder name="auditd-syscall">
  <parent>auditd</parent>
  <regex type="pcre2">type=PROCTITLE msg=audit\([^)]+\): proctitle=([0-9A-F]+)(?=\s|$)</regex>
  <order>audit.proctitle_hex</order>
</decoder>

Step 3. Exclude the original file. In the single-node Docker deployment, ossec.conf is mounted from config/wazuh_cluster/wazuh_manager.conf on the Docker host. Add this line inside <ruleset>, below <decoder_dir>ruleset/decoders</decoder_dir>:

  <decoder_exclude>ruleset/decoders/0040-auditd_decoders.xml</decoder_exclude>

Then restart the manager container (docker compose restart wazuh.manager). On startup the container copies the mounted configuration into /var/ossec/etc.

The alerts keep everything the default decoder extracts and also get these fields when a value is hex-encoded, for example:

  data.audit.execve.a2_hex: 2F62696E2F6563686F202268656C6C6F20776F726C6422
  data.audit.proctitle_hex: 62617368002D63002F62696E2F6563686F202268656C6C6F20776F726C6422

Step 4. Rules on the command content. Since the value is still hex, the rule has to match the hex-encoded text. You can get the hex of any string with:

  printf '%s' 'hello world' | xxd -p -c 256 | tr a-f A-F
  68656C6C6F20776F726C64

These are the two rules we validated (add them to /var/ossec/etc/rules/local_rules.xml and adjust the IDs and patterns). The first one checks every argument of the EXECVE record, the second one checks a single extracted field (the "sh -c" payload):

<group name="audit,audit_command,">

  <rule id="100200" level="8">
    <if_sid>80792</if_sid>
    <field name="audit.key">^user_cmd$</field>
    <regex type="pcre2">type=EXECVE msg=audit\([^)]+\): argc=\d+.*? a\d+=(?:[0-9A-F]{2})*?68656C6C6F20776F726C64</regex>
    <description>Audit: command with "hello world" in its arguments run by a monitored account.</description>
  </rule>

  <rule id="100201" level="10">
    <if_sid>80792</if_sid>
    <field name="audit.execve.a1">^-c$</field>
    <field name="audit.execve.a2_hex" type="pcre2">^(?:[0-9A-F]{2})*?C3B1616E64C3BA</field>
    <description>Audit: shell command containing "ñandú" run by a monitored account.</description>
  </rule>

</group>

The "(?:[0-9A-F]{2})*?" part keeps the match aligned to whole bytes, so a pattern cannot match across two characters. auditd writes the hex in uppercase, and multibyte UTF-8 characters work as well (C3B1616E64C3BA is "ñandú").

You can test the decoder and the rules with wazuh-logtest, pasting a full_log line from one of your alerts.


Option B: decode the values in the indexer to search them in the dashboard

This is done with an ingest pipeline that runs a painless script. It requires option A, because it decodes the aN_hex and proctitle_hex fields.

Note: the Wazuh Filebeat pipeline routes alerts to the daily index with a date_index_name processor, and because of that an index.final_pipeline setting on wazuh-alerts-4.x-* is never executed (we tested it). The approach that works is to create a separate pipeline and call it from the end of the Wazuh Filebeat pipeline.

Step 1. Save this as audit-hex-decode-pipeline.json:

{
  "description": "Decode hex-encoded auditd EXECVE arguments and PROCTITLE (custom)",
  "processors": [
    {
      "script": {
        "tag": "audit_hex_decode",
        "if": "ctx.data?.audit != null",
        "lang": "painless",
        "source": "String hexToString(String h, boolean nulToSpace) {\n  if (h == null || h.length() % 2 != 0) { return null; }\n  byte[] b = new byte[h.length() / 2];\n  for (int i = 0; i < b.length; i++) {\n    b[i] = (byte) Integer.parseInt(h.substring(2 * i, 2 * i + 2), 16);\n    if (nulToSpace && b[i] == 0) { b[i] = 32; }\n  }\n  return Base64.getEncoder().encodeToString(b).decodeBase64();\n}\nMap audit = ctx.data.audit;\nif (audit.execve instanceof Map) {\n  Map ex = audit.execve;\n  for (int i = 0; i < 8; i++) {\n    String k = 'a' + i + '_hex';\n    if (ex.containsKey(k) && !ex.containsKey('a' + i)) {\n      String v = hexToString(ex[k], false);\n      if (v != null) { ex['a' + i] = v; }\n    }\n  }\n}\nif (audit.proctitle_hex != null) {\n  String v = hexToString(audit.proctitle_hex, true);\n  if (v != null) { audit.proctitle = v.trim(); }\n}",
        "ignore_failure": true
      }
    }
  ]
}

Step 2. Create the pipeline and add it at the end of the Wazuh Filebeat pipeline (replace the indexer address and the password; jq is required):

  curl -sk -u admin:<password> -X PUT "https://<indexer>:9200/_ingest/pipeline/wazuh-audit-hex-decode" -H 'Content-Type: application/json' -d @audit-hex-decode-pipeline.json

  curl -sk -u admin:<password> "https://<indexer>:9200/_ingest/pipeline/filebeat-7.10.2-wazuh-alerts-pipeline" -o wazuh-alerts-pipeline.json

  jq '.["filebeat-7.10.2-wazuh-alerts-pipeline"] | .processors += [{"pipeline":{"name":"wazuh-audit-hex-decode","if":"ctx.data?.audit != null","ignore_failure":true}}]' wazuh-alerts-pipeline.json > wazuh-alerts-pipeline-patched.json

  curl -sk -u admin:<password> -X PUT "https://<indexer>:9200/_ingest/pipeline/filebeat-7.10.2-wazuh-alerts-pipeline" -H 'Content-Type: application/json' -d @wazuh-alerts-pipeline-patched.json

Keep wazuh-alerts-pipeline.json as a backup in case you need to roll back.

New alerts get readable fields next to the hex ones, for example:

  data.audit.execve.a2: /bin/echo "hello world" "foo bar"; /bin/echo "ñandú"
  data.audit.proctitle: bash -c /bin/echo "hello world"

They are indexed as keyword fields, so you can search them in the dashboard, for example data.audit.execve.a2:*hello* (wildcard searches on keyword fields are case sensitive). If the new fields do not show up in Discover, refresh the wazuh-alerts-* index pattern.

Considerations:
- The decoding happens only when the alert is indexed, so rules on the manager cannot use the decoded values (use option A or C for rules). Alerts that are already indexed are not modified.
- Upgrading Wazuh, or running filebeat setup --pipelines, can overwrite the Wazuh Filebeat pipeline. In that case repeat Step 2. The wazuh-audit-hex-decode pipeline itself is not affected.


Option C: LAUREL and the JSON decoder

LAUREL runs as an auditd plugin and writes each audit event as a single JSON line, with the arguments already decoded (EXECVE.ARGV is a list) and extra context such as the parent process (SPAWNED_BY). We tested LAUREL 0.8.2 with its default configuration, installed following the project instructions.

Step 1. On the agent, monitor the LAUREL output with the JSON log format:

  <localfile>
    <log_format>json</log_format>
    <location>/var/log/laurel/audit.log</location>
  </localfile>

Step 2. Add your own rules. There is no official Wazuh ruleset for the LAUREL output, so the default auditd rules (including 80792) do not apply to these events. These are the rules we validated, the base rule reuses your audit-keys list the same way rule 80792 does:

<group name="audit,laurel,">

  <rule id="100300" level="3">
    <decoded_as>json</decoded_as>
    <field name="EXECVE.argc">\d+</field>
    <list field="SYSCALL.key" lookup="match_key_value" check_value="command">etc/lists/audit-keys</list>
    <description>LAUREL: command run by a monitored account: $(EXECVE.ARGV)</description>
    <group>audit_command,</group>
  </rule>

  <rule id="100301" level="8">
    <if_sid>100300</if_sid>
    <field name="EXECVE.ARGV">hello world</field>
    <description>LAUREL: command with "hello world" in its arguments run by a monitored account.</description>
  </rule>

</group>

The alert contains the full command, for example data.EXECVE.ARGV: ["bash", "-c", "/bin/echo \"hello world\" laurel-live"], and the field is searchable in the dashboard.

Considerations:
- Use a single source for these commands. If the agent reads both /var/log/audit/audit.log and the LAUREL log, each command generates two alerts, one from rule 80792 and one from the LAUREL rules.
- Any other auditd rule you rely on (0365-auditd_rules.xml) would need an equivalent written for the JSON fields.
- LAUREL events contain both numeric fields (auid) and resolved names (AUID). In our tests, $(SYSCALL.AUID) in a rule description returned the numeric value, so check a field before using it in descriptions.


4. Is there a reference ruleset for the LAUREL JSON output?

No, there is no official one, this is what issue #14461 requests and it is still open: https://github.com/wazuh/wazuh/issues/14461

The rules in option C can be used as a starting point.


Additional notes

- Use EXECVE, not PROCTITLE, as the source of the executed command. The kernel truncates PROCTITLE to 128 bytes, and in some cases (for example when bash -c executes its last command directly) the PROCTITLE record shows the command line of the calling shell instead of the executed program.
- auditd splits very long arguments into several fields (a2_len, a2[0], a2[1], and so on). The decoders above do not cover that format.
- The decoders above cover arguments a0 to a7, the same as the default decoder.
- Excluding the original decoder file means it will not receive updates in future upgrades, so compare it with the new default file after each upgrade.


References

- Modifying default decoders: https://documentation.wazuh.com/current/user-manual/ruleset/decoders/custom.html
- Sibling decoders: https://documentation.wazuh.com/current/user-manual/ruleset/decoders/sibling-decoders.html
- Decoders syntax (regex type pcre2): https://documentation.wazuh.com/current/user-manual/ruleset/ruleset-xml-syntax/decoders.html
- Rules syntax: https://documentation.wazuh.com/current/user-manual/ruleset/ruleset-xml-syntax/rules.html
- CDB lists: https://documentation.wazuh.com/current/user-manual/ruleset/cdb-list.html
- JSON decoder: https://documentation.wazuh.com/current/user-manual/ruleset/decoders/json-decoder.html
- Testing decoders and rules: https://documentation.wazuh.com/current/user-manual/ruleset/testing.html
- Ruleset configuration (decoder_exclude): https://documentation.wazuh.com/current/user-manual/reference/ossec-conf/ruleset.html
- Localfile configuration: https://documentation.wazuh.com/current/user-manual/reference/ossec-conf/localfile.html
- Auditd configuration: https://documentation.wazuh.com/current/user-manual/capabilities/system-calls-monitoring/audit-configuration.html
- Wazuh Docker container usage: https://documentation.wazuh.com/current/deployment-options/docker/container-usage.html
- LAUREL: https://github.com/threathunters-io/laurel

Hextra Srl

unread,
Sep 29, 2026, 11:53:36 AMSep 29
to Wazuh | Mailing List
Hi Diego,

thanks a lot for the detailed answer, very helpful.

For now our goal is only to track/read the commands, not to write rules on them, so we went with a lighter variant of Option B without Option A (no custom decoders on the manager).

We added a single script processor (ignore_failure: true) to filebeat-7.10.2-wazuh-alerts-pipeline, right after the json processor and our existing rename for a Nextcloud ingest issue. It reads the EXECVE part directly from full_log, takes each aN (quoted values as-is, hex values decoded) and joins them into a new field data.audit.cmdline. The rest of the default pipeline is unchanged.

{
  "script": {
    "tag": "hextra-audit-cmdline",
    "if": "ctx.full_log != null && ctx.full_log.contains('type=EXECVE ') && ctx.data?.audit != null",
    "lang": "painless",
    "ignore_failure": true,
    "source": "String hex2str(String h) { if (h == null || h.length() == 0 || h.length() % 2 != 0) { return null; } for (int i = 0; i < h.length(); i++) { char c = h.charAt(i); boolean ok = (c >= (char)'0' && c <= (char)'9') || (c >= (char)'A' && c <= (char)'F'); if (!ok) { return null; } } byte[] b = new byte[h.length() / 2]; for (int i = 0; i < b.length; i++) { int v = Integer.parseInt(h.substring(2 * i, 2 * i + 2), 16); if (v == 0) { v = 32; } b[i] = (byte) v; } return Base64.getEncoder().encodeToString(b).decodeBase64(); }\nString fl = ctx.full_log;\nint e = fl.indexOf('type=EXECVE ');\nint end = fl.indexOf(' type=', e + 12);\nString ex = end > 0 ? fl.substring(e, end) : fl.substring(e);\nint ai = ex.indexOf('argc=');\nif (ai < 0) { return; }\nint ae = ex.indexOf(' ', ai);\nint argc = Integer.parseInt(ae > 0 ? ex.substring(ai + 5, ae) : ex.substring(ai + 5));\nList parts = new ArrayList();\nfor (int i = 0; i < argc && i < 64; i++) {\n  String k = ' a' + i + '=';\n  int p = ex.indexOf(k);\n  if (p < 0) { parts.add('[a' + i + ' non disponibile]'); continue; }\n  p += k.length();\n  if (p < ex.length() && ex.charAt(p) == (char)'\"') {\n    int q = ex.indexOf('\"', p + 1);\n    parts.add(q > 0 ? ex.substring(p + 1, q) : ex.substring(p + 1));\n  } else {\n    int q = ex.indexOf(' ', p);\n    String h = q > 0 ? ex.substring(p, q) : ex.substring(p);\n    String d = hex2str(h);\n    parts.add(d != null ? d : h);\n  }\n}\nctx.data.audit.cmdline = String.join(' ', parts);"
  }
}

Tested with _simulate first, then applied. Result on a real alert:

a2=6563686F2074657374206D617474656F -> data.audit.cmdline: "sh -c echo test matteo"

full_log stays untouched and alerts keep flowing normally.

We know it has to be re-applied after upgrades / filebeat setup --pipelines, and that split arguments (aN_len / aN[0]...) are not handled.

Do you see any other drawbacks with this approach (performance, mapping of the new field, anything else)?

Thanks again!

diego...@wazuh.com

unread,
Sep 29, 2026, 1:03:47 PMSep 29
to Wazuh | Mailing List
Hextra Srl

Thanks for sharing the processor. We tested it as is against real auditd events from our lab, grouped the same way the Wazuh logcollector sends them. It works as expected: hex values (including UTF-8), quoted values and "sh -c" payloads are decoded correctly, and non-audit events are left untouched. Overall it is a good lightweight approach if you only need to read the commands.

These are the points we found worth considering:

1. Commands longer than about 7.5 KB lose more than just split arguments

The kernel limits each EXECVE record to about 7500 bytes (MAX_EXECVE_AUDIT_LEN). When a command line does not fit, the argument that crosses the limit is split (aN_len, aN[0], aN[1]) and every argument after it goes to additional EXECVE records. Your script only parses the first EXECVE record, so those later arguments are also reported as missing, even when they are short.

Example from our tests, /bin/echo with four arguments of 3000 characters:

  type=EXECVE ... argc=5 a0="/bin/echo" a1="xxx..." a2="xxx..." a3_len=3000 a3[0]="xxx..."
  type=EXECVE ... a3[1]="xxx..." a4="xxx..."

  data.audit.cmdline: /bin/echo xxx... xxx... [a3 non disponibile] [a4 non disponibile]

If this matters for you, the script can be extended to concatenate all the EXECVE records found in full_log and to join the aN[k] chunks when aN_len is present.

2. Failures are silent with ignore_failure

With "ignore_failure": true, a script error is not counted anywhere: in our tests the processor "failed" counter in the ingest stats stayed at 0, and the alert was indexed without data.audit.cmdline. The alert keeps flowing, but you would not notice the problem. An alternative we tested is to remove "ignore_failure" and add an on_failure handler that records the error:

  "on_failure": [
    { "set": { "field": "data.audit.cmdline_error", "value": "{{ _ingest.on_failure_message }}" } }
  ]

The alert is still indexed normally, and you can find any failed conversion in the dashboard with data.audit.cmdline_error:*

3. Performance

The overhead is very low. In a quick benchmark in our lab, the processor took about 24 ms in total for 2000 audit events (around 0.012 ms per event), and events without "type=EXECVE " only pay for the "if" condition. You can check the real numbers on your indexer with:

  GET _nodes/stats/ingest?filter_path=nodes.*.ingest.pipelines.filebeat-7.10.2-wazuh-alerts-pipeline

and look for the processor entry containing "hextra-audit-cmdline" (count, time_in_millis).

4. Mapping of the new field

data.audit.cmdline is not defined in the Wazuh template, so it is mapped dynamically as keyword by the template's "string_as_keyword" dynamic template, the same as other string fields under data. This means:

- Searches are exact or wildcard (for example data.audit.cmdline:*matteo*), not full-text, and they are case sensitive.
- Leading wildcard searches are more expensive, so narrowing the time range helps on large indices.
- The dynamic mapping has no ignore_above limit, but because only the first EXECVE record is parsed, the value stays well below the 32766-byte limit of a keyword term, so there is no risk of rejected documents.
- If the field does not show up in Discover, refresh the field list of the wazuh-alerts-* index pattern.

5. Other considerations

- Argument boundaries are lost when joining with spaces (for example "a b" as one argument and a b as two arguments look the same). full_log remains the source of truth. If you ever need them, you can also store the parts list as an array field.
- Command lines can contain secrets passed as arguments (passwords, tokens). They were already recoverable from full_log, but now they are readable and searchable, so it is worth reviewing who has access to the wazuh-alerts-* indices and the dashboard.
- The processor only applies to alerts. If you enable archives indexing at some point, the archives use a separate pipeline (filebeat-7.10.2-wazuh-archives-pipeline) that would need the same processor.
- As you mentioned, it needs to be re-applied after upgrades or after running filebeat setup --pipelines. Keeping the processor JSON under version control and checking the pipeline with GET _ingest/pipeline/filebeat-7.10.2-wazuh-alerts-pipeline after each upgrade makes that easy.

References

- Wazuh indexer indices: https://documentation.wazuh.com/current/user-manual/wazuh-indexer/wazuh-indexer-indices.html
- Wazuh role-based access control: https://documentation.wazuh.com/current/user-manual/user-administration/rbac.html
- Handling pipeline failures (on_failure): https://docs.opensearch.org/latest/ingest-pipelines/handling-pipeline-failures/
- Script processor: https://docs.opensearch.org/latest/ingest-pipelines/processors/script/
- Simulate a pipeline: https://docs.opensearch.org/latest/ingest-pipelines/simulate-ingest/
- Nodes stats API (ingest stats): https://docs.opensearch.org/latest/api-reference/nodes-apis/nodes-stats/

Regards,
Reply all
Reply to author
Forward
0 new messages