Store numbers as number_value; fix conditional guards to != ""

Two corrections confirmed against Google's official content-hub parsers
(content/parsers/third_party/community/*/cbn):

1. Numbers as numbers. count, source_asn, successful_session_count and
   failed_session_count were being stored in additional.fields as string_value.
   Store them as number_value instead (build string -> convert to uinteger ->
   rename to number_value, the content-hub idiom), so SecOps can range-query and
   sort them, per parsedmarc's "store numbers as numbers" rule. Booleans stay
   string_value (content-hub never uses bool_value) and are still converted in
   step 1b for the == "true"/"false" comparisons.

2. Conditional guards. Replaced bare `if [field] {` with `if [field] != "" {`
   (76 guards + the detection cascade + policy_override). After 1a initializes
   every tested field to "", a bare `if` is true for an empty field (Logstash/CBN
   semantics), which would misfire detection and emit empty labels. content-hub
   uses `!= ""` ~111x vs 2 bare (both flags); parser flags (no_json_payload,
   not_json, *_nan) correctly stay bare.

Verified: braces balance, no stray bare field-guards, all if-tested fields
initialized, all four numeric fields emit number_value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sean Whalen
2026-07-11 13:58:10 -04:00
co-authored by Claude Opus 4.8
parent d8b8186328
commit 53402c28fe
2 changed files with 151 additions and 121 deletions
+17 -11
View File
@@ -48,17 +48,23 @@ DMARC types.
## Caveats
1. **Unvalidated** — see [Status](#status).
2. **JSON type handling** — parsedmarc emits `dmarc_aligned` / `spf_aligned` /
`dkim_aligned` / `testing` / `normalized_timespan` as JSON booleans and
`count` / `*_session_count` / `source_asn` as numbers. Chronicle's `json{}`
filter **preserves the original JSON type**, so the parser explicitly
converts these to strings (`mutate { convert => { … => "string" } }`) before
any comparison — otherwise `[dmarc_aligned] == "false"` would never match.
Relatedly, every field tested in an `if` is initialized to `""` *before* the
`json` filter, because CBN raises `_failed_parsing_` on a conditional that
references a field absent from the log. A DMARC-fail record
(`dmarc_aligned=false`) should yield `security_result.category =
AUTH_VIOLATION` — still worth confirming in the validation tool.
2. **JSON types** — Chronicle's `json{}` filter **preserves the original JSON
type**, so parsedmarc's booleans and numbers are handled differently:
- **Booleans** (`dmarc_aligned` / `spf_aligned` / `dkim_aligned` / `testing`
/ `normalized_timespan`) are converted to strings so `[dmarc_aligned] ==
"false"` works, and stored as `string_value` (Google's content-hub parsers
never use `bool_value`).
- **Numbers** (`count` / `*_session_count` / `source_asn`) are stored as
`number_value` — built as a string, `convert`-ed to `uinteger`, then
renamed — so SecOps can range-query and sort them (parsedmarc's "store
numbers as numbers" rule).
Every `if`-tested field is initialized to `""` *before* `json` and guarded
with `!= ""`: CBN raises `_failed_parsing_` on a conditional referencing an
absent field, and treats an initialized-but-empty field as present. A
DMARC-fail record (`dmarc_aligned=false`) should yield
`security_result.category = AUTH_VIOLATION` — worth confirming in the
validation tool.
3. **Aggregate count** — a DMARC aggregate record summarizes `count` messages
from one source IP, not a single message. Each record becomes one
`EMAIL_TRANSACTION` with `count` carried in `additional.fields`. There is no