Store CBN parser booleans as typed bool_value

The alignment flags and normalized_timespan were stored as
string_value "true"/"false" in additional.fields, which diverged from
the [gsecops] API output (a protobuf Struct, where they are real
booleans) — a UDM search written for one delivery path would not match
the other for those four fields.

Google's parser extension examples document the boolean idiom this
uses: build a string, convert to boolean, rename into the boolean
field. It is the boolean analogue of the number_value chain already
used for count/source_asn (proven in the content-hub Azure Cosmos DB
parser), and the UDM search docs confirm additional fields are
matchable via value.bool_value. The string conversion in step 1b
remains, because CBN conditionals compare against the preserved JSON
type and the == "true"/"false" guards need strings.

With this, both delivery paths emit identical value types for every
additional.fields key. The prior justification ("content-hub never
uses bool_value") described the absence of precedent, not a
prohibition; the documented boolean example is the stronger authority.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sean Whalen
2026-07-11 15:32:12 -04:00
co-authored by Claude Fable 5
parent fba083f351
commit c8e8cb54c0
2 changed files with 64 additions and 17 deletions
+15 -6
View File
@@ -71,11 +71,16 @@ DMARC types.
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` /
`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`). Note that `testing` is **not** a boolean —
parsedmarc emits the RFC 9990 `t=` flag as the string `"y"`/`"n"` — so it
is passed through as a string and guarded with `!= ""`.
`normalized_timespan`) are converted to strings because CBN conditionals
compare against the preserved type, so `[dmarc_aligned] == "false"` needs
a string. In `additional.fields` they are stored as typed **`bool_value`**
— built as a string, `convert`-ed back to `boolean`, then renamed — the
boolean pattern from Google's
[parser extension examples](https://docs.cloud.google.com/chronicle/docs/event-processing/parser-extension-examples);
UDM search matches them with `value.bool_value`. Note that `testing` is
**not** a boolean — parsedmarc emits the RFC 9990 `t=` flag as the string
`"y"`/`"n"` — so it is passed through as a string and guarded with
`!= ""`.
- **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
@@ -83,6 +88,9 @@ DMARC types.
so a tenant where `%{}` rejects non-string fields degrades to a missing
`additional.fields` entry instead of `_failed_parsing_`.
Both value types match what the `[gsecops]` API output emits, so UDM
searches and dashboards port between the two delivery paths unchanged.
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
@@ -205,7 +213,8 @@ Suggested order:
depends on.
2. The two aggregates — confirm the `dmarc_aligned=false` one yields
`security_result.category = AUTH_VIOLATION`, that `count` and `source_asn`
land as `number_value`, and that the boolean→string handling holds.
land as `number_value`, and that the alignment booleans land as
`bool_value` (queryable as `additional.fields["dmarc_aligned"].value.bool_value`).
3. The SMTP TLS rows — confirm the success row produces a `GENERIC_EVENT` with
`target.hostname` and no `security_result`, and the failure rows produce
`security_result.action = FAIL`.