From d5ceb220d2052573aea3dbfc829f293e0ecd3bcf Mon Sep 17 00:00:00 2001 From: zanarelli Date: Thu, 30 Jul 2026 21:50:44 -0300 Subject: [PATCH 1/8] in_kubernetes_events: fix OOB reads from non-NUL-terminated msgpack strings record_get_field_uint64() and record_get_field_time() called strtoul()/flb_strptime() directly on msgpack_object.via.str.ptr. msgpack strings are raw, length-prefixed bytes into the decode buffer, not NUL-terminated, so these C-string functions could read past the field's true boundary. record_get_field_ptr()'s strncmp() key match had the same latent issue (a key that is a prefix of fieldname could false-match, and a short key could still be read past its bounds by strncmp with a longer fieldname length). A spec-compliant Kubernetes Event field (e.g. resourceVersion as a digit-only JSON string) placed at the edge of the decode buffer is enough to trigger an out-of-bounds read; confirmed via a guard-page harness that reproduces EXC_BAD_ACCESS inside strtoul_l, called from record_get_field_uint64. This is the same bug class fixed same-day for the sibling out_stackdriver plugin (#12022, backported in #12170), and the same class that produced GHSA-5rjf-prwh-pp7q in this project before. Applies the same fix pattern here: copy the field into a bounded, NUL-terminated stack buffer before parsing, and require an exact length match before the key strncmp. A prior contributor flagged the same underlying issue in #12073, but it was self-closed without a fix landing; the vulnerable code is still present at HEAD. Signed-off-by: zanarelli --- .../in_kubernetes_events/kubernetes_events.c | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/plugins/in_kubernetes_events/kubernetes_events.c b/plugins/in_kubernetes_events/kubernetes_events.c index 3d996806b54..9c4bd20b16f 100644 --- a/plugins/in_kubernetes_events/kubernetes_events.c +++ b/plugins/in_kubernetes_events/kubernetes_events.c @@ -179,6 +179,7 @@ static int refresh_token_if_needed(struct k8s_events *ctx) static msgpack_object *record_get_field_ptr(msgpack_object *obj, const char *fieldname) { int i; + size_t fieldname_len; msgpack_object *k; msgpack_object *v; @@ -186,13 +187,23 @@ static msgpack_object *record_get_field_ptr(msgpack_object *obj, const char *fie return NULL; } + fieldname_len = strlen(fieldname); + for (i = 0; i < obj->via.map.size; i++) { k = &obj->via.map.ptr[i].key; if (k->type != MSGPACK_OBJECT_STR) { continue; } - if (strncmp(k->via.str.ptr, fieldname, strlen(fieldname)) == 0) { + /* + * msgpack strings are not NUL terminated: k->via.str.ptr points + * directly into the decode buffer for exactly k->via.str.size + * bytes. Require an exact length match before comparing so we + * never read past that boundary, and so a key that merely shares + * a prefix with fieldname cannot match. + */ + if ((size_t) k->via.str.size == fieldname_len && + strncmp(k->via.str.ptr, fieldname, fieldname_len) == 0) { v = &obj->via.map.ptr[i].val; return v; } @@ -220,6 +231,7 @@ static int record_get_field_time(msgpack_object *obj, const char *fieldname, str { msgpack_object *v; struct flb_tm tm = { 0 }; + char buf[64]; v = record_get_field_ptr(obj, fieldname); if (v == NULL) { @@ -229,7 +241,19 @@ static int record_get_field_time(msgpack_object *obj, const char *fieldname, str return -1; } - if (flb_strptime(v->via.str.ptr, "%Y-%m-%dT%H:%M:%SZ", &tm) == NULL) { + /* + * msgpack strings are not NUL terminated: v->via.str.ptr points + * directly into the decode buffer for exactly v->via.str.size bytes. + * Copy it into a bounded, NUL-terminated stack buffer before handing + * it to flb_strptime(), instead of scanning the raw buffer directly. + */ + if (v->via.str.size == 0 || v->via.str.size >= sizeof(buf)) { + return -2; + } + memcpy(buf, v->via.str.ptr, v->via.str.size); + buf[v->via.str.size] = '\0'; + + if (flb_strptime(buf, "%Y-%m-%dT%H:%M:%SZ", &tm) == NULL) { return -2; } @@ -242,7 +266,9 @@ static int record_get_field_time(msgpack_object *obj, const char *fieldname, str static int record_get_field_uint64(msgpack_object *obj, const char *fieldname, uint64_t *val) { msgpack_object *v; + char buf[32]; char *end; + size_t len; v = record_get_field_ptr(obj, fieldname); if (v == NULL) { @@ -251,8 +277,22 @@ static int record_get_field_uint64(msgpack_object *obj, const char *fieldname, u /* attempt to parse string as number... */ if (v->type == MSGPACK_OBJECT_STR) { - *val = strtoul(v->via.str.ptr, &end, 10); - if (end == NULL || (end < v->via.str.ptr + v->via.str.size)) { + /* + * msgpack strings are not NUL terminated: v->via.str.ptr points + * directly into the decode buffer for exactly v->via.str.size + * bytes. Copy it into a bounded, NUL-terminated stack buffer + * before calling strtoul() on it, instead of scanning the raw + * buffer directly (no valid uint64 needs more than 20 digits). + */ + len = v->via.str.size; + if (len == 0 || len > sizeof(buf) - 1) { + return -1; + } + memcpy(buf, v->via.str.ptr, len); + buf[len] = '\0'; + + *val = strtoul(buf, &end, 10); + if (end == NULL || end == buf || *end != '\0') { return -1; } return 0; From f8c40d61c8877b75f39c547aff5ef608bf91a9fe Mon Sep 17 00:00:00 2001 From: zanarelli Date: Fri, 31 Jul 2026 06:27:49 -0300 Subject: [PATCH 2/8] in_kubernetes_events: tighten timestamp and uint64 parsing validation Require flb_strptime to consume the full copied buffer, treat only ret==0 as a successful timestamp in item_get_timestamp(), and parse uint64 strings with strtoull+errno so malformed or overflowing values fall through to the next timestamp field instead of being accepted. Signed-off-by: Raphael Zanarelli Signed-off-by: zanarelli --- .../in_kubernetes_events/kubernetes_events.c | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/in_kubernetes_events/kubernetes_events.c b/plugins/in_kubernetes_events/kubernetes_events.c index 9c4bd20b16f..c6b874397b8 100644 --- a/plugins/in_kubernetes_events/kubernetes_events.c +++ b/plugins/in_kubernetes_events/kubernetes_events.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -253,8 +254,13 @@ static int record_get_field_time(msgpack_object *obj, const char *fieldname, str memcpy(buf, v->via.str.ptr, v->via.str.size); buf[v->via.str.size] = '\0'; - if (flb_strptime(buf, "%Y-%m-%dT%H:%M:%SZ", &tm) == NULL) { - return -2; + { + char *end; + + end = flb_strptime(buf, "%Y-%m-%dT%H:%M:%SZ", &tm); + if (end == NULL || *end != '\0') { + return -2; + } } val->tm.tv_sec = flb_parser_tm2time(&tm, FLB_FALSE); @@ -291,8 +297,9 @@ static int record_get_field_uint64(msgpack_object *obj, const char *fieldname, u memcpy(buf, v->via.str.ptr, len); buf[len] = '\0'; - *val = strtoul(buf, &end, 10); - if (end == NULL || end == buf || *end != '\0') { + errno = 0; + *val = strtoull(buf, &end, 10); + if (errno == ERANGE || end == buf || *end != '\0') { return -1; } return 0; @@ -302,8 +309,7 @@ static int record_get_field_uint64(msgpack_object *obj, const char *fieldname, u return 0; } if (v->type == MSGPACK_OBJECT_NEGATIVE_INTEGER) { - *val = (uint64_t)v->via.i64; - return 0; + return -1; } return -1; } @@ -317,12 +323,12 @@ static int item_get_timestamp(msgpack_object *obj, struct flb_time *event_time) * NULL while having metadata.creationTimestamp set. */ ret = record_get_field_time(obj, "lastTimestamp", event_time); - if (ret != -1) { + if (ret == 0) { return FLB_TRUE; } ret = record_get_field_time(obj, "firstTimestamp", event_time); - if (ret != -1) { + if (ret == 0) { return FLB_TRUE; } @@ -332,7 +338,7 @@ static int item_get_timestamp(msgpack_object *obj, struct flb_time *event_time) } ret = record_get_field_time(metadata, "creationTimestamp", event_time); - if (ret != -1) { + if (ret == 0) { return FLB_TRUE; } From c9755730719fa33f8bdcb13ca9b79f25dbf3fa7f Mon Sep 17 00:00:00 2001 From: zanarelli Date: Fri, 31 Jul 2026 12:53:38 -0300 Subject: [PATCH 3/8] in_kubernetes_events: reject signed uint64 strings in record_get_field_uint64 strtoull() itself accepts a leading '+'/'-' and skips leading whitespace, so a resourceVersion string like "-5" silently wrapped around into 18446744073709551611 instead of being rejected. Kubernetes always serializes resourceVersion as a plain unsigned digits-only decimal string, so require the first byte to be a digit before calling strtoull(), on top of the existing errno/ERANGE and full-consumption checks. Confirmed via a standalone guard-page harness (same shape as the existing OOB reproduction in this PR): "-5" and "+5" are now rejected (previously accepted, wrapping "-5" to UINT64_MAX-4), a valid digits-only value still round-trips correctly, and overflow beyond UINT64_MAX still correctly fails via the existing ERANGE check. Addresses a CodeRabbit review comment on this PR. Signed-off-by: Raphael Zanarelli Signed-off-by: zanarelli --- plugins/in_kubernetes_events/kubernetes_events.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/plugins/in_kubernetes_events/kubernetes_events.c b/plugins/in_kubernetes_events/kubernetes_events.c index c6b874397b8..9ac2e5e0047 100644 --- a/plugins/in_kubernetes_events/kubernetes_events.c +++ b/plugins/in_kubernetes_events/kubernetes_events.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -297,6 +298,18 @@ static int record_get_field_uint64(msgpack_object *obj, const char *fieldname, u memcpy(buf, v->via.str.ptr, len); buf[len] = '\0'; + /* + * strtoull() itself accepts a leading '+'/'-' and skips leading + * whitespace, which would let a value like "-5" silently wrap + * around into a huge positive number instead of being rejected. + * Kubernetes resourceVersion (the only caller) is always a plain, + * unsigned, digits-only decimal string, so require that directly + * before parsing. + */ + if (!isdigit((unsigned char) buf[0])) { + return -1; + } + errno = 0; *val = strtoull(buf, &end, 10); if (errno == ERANGE || end == buf || *end != '\0') { From 77d2063e14f161bee7dc499f87127121d4b52fe9 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 4 Aug 2026 08:46:39 -0600 Subject: [PATCH 4/8] in_kubernetes_events: complete bounded field lookup Signed-off-by: Eduardo Silva --- .../in_kubernetes_events/kubernetes_events.c | 43 ++++++------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/plugins/in_kubernetes_events/kubernetes_events.c b/plugins/in_kubernetes_events/kubernetes_events.c index 9ac2e5e0047..da4c9010d03 100644 --- a/plugins/in_kubernetes_events/kubernetes_events.c +++ b/plugins/in_kubernetes_events/kubernetes_events.c @@ -231,6 +231,7 @@ static int record_get_field_sds(msgpack_object *obj, const char *fieldname, flb_ static int record_get_field_time(msgpack_object *obj, const char *fieldname, struct flb_time *val) { + char *end; msgpack_object *v; struct flb_tm tm = { 0 }; char buf[64]; @@ -255,13 +256,9 @@ static int record_get_field_time(msgpack_object *obj, const char *fieldname, str memcpy(buf, v->via.str.ptr, v->via.str.size); buf[v->via.str.size] = '\0'; - { - char *end; - - end = flb_strptime(buf, "%Y-%m-%dT%H:%M:%SZ", &tm); - if (end == NULL || *end != '\0') { - return -2; - } + end = flb_strptime(buf, "%Y-%m-%dT%H:%M:%SZ", &tm); + if (end == NULL || *end != '\0') { + return -2; } val->tm.tv_sec = flb_parser_tm2time(&tm, FLB_FALSE); @@ -570,7 +567,6 @@ static int process_event_list(struct k8s_events *ctx, char *in_data, size_t in_s size_t off = 0; msgpack_unpacked result; msgpack_object root; - msgpack_object k; msgpack_object *items = NULL; msgpack_object *item = NULL; msgpack_object *metadata = NULL; @@ -599,27 +595,16 @@ static int process_event_list(struct k8s_events *ctx, char *in_data, size_t in_s /* Traverse the EventList for the metadata (for the continue token) and the items. * https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/event-v1/#EventList */ - for (i = 0; i < root.via.map.size; i++) { - k = root.via.map.ptr[i].key; - if (k.type != MSGPACK_OBJECT_STR) { - continue; - } - - if (strncmp(k.via.str.ptr, "items", 5) == 0) { - items = &root.via.map.ptr[i].val; - if (items->type != MSGPACK_OBJECT_ARRAY) { - flb_plg_error(ctx->ins, "Cannot unpack items"); - goto msg_error; - } - } + items = record_get_field_ptr(&root, "items"); + if (items != NULL && items->type != MSGPACK_OBJECT_ARRAY) { + flb_plg_error(ctx->ins, "Cannot unpack items"); + goto msg_error; + } - if (strncmp(k.via.str.ptr, "metadata", 8) == 0) { - metadata = &root.via.map.ptr[i].val; - if (metadata->type != MSGPACK_OBJECT_MAP) { - flb_plg_error(ctx->ins, "Cannot unpack metadata"); - goto msg_error; - } - } + metadata = record_get_field_ptr(&root, "metadata"); + if (metadata != NULL && metadata->type != MSGPACK_OBJECT_MAP) { + flb_plg_error(ctx->ins, "Cannot unpack metadata"); + goto msg_error; } if (items == NULL) { @@ -757,7 +742,7 @@ static int k8s_events_sql_insert_event(struct k8s_events *ctx, msgpack_object *i flb_sds_t uid; - meta = record_get_field_ptr(item, "meta"); + meta = record_get_field_ptr(item, "metadata"); if (meta == NULL) { flb_plg_error(ctx->ins, "unable to find metadata to save event"); return -1; From 87bbdc638dc674d74d23059a14f9a750d1453162 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 4 Aug 2026 08:46:43 -0600 Subject: [PATCH 5/8] in_kubernetes_events: finalize database statements Signed-off-by: Eduardo Silva --- plugins/in_kubernetes_events/kubernetes_events_conf.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/in_kubernetes_events/kubernetes_events_conf.c b/plugins/in_kubernetes_events/kubernetes_events_conf.c index 26ccc850ad3..315a07a378c 100644 --- a/plugins/in_kubernetes_events/kubernetes_events_conf.c +++ b/plugins/in_kubernetes_events/kubernetes_events_conf.c @@ -362,6 +362,9 @@ void k8s_events_conf_destroy(struct k8s_events *ctx) #ifdef FLB_HAVE_SQLDB if (ctx->db) { + sqlite3_finalize(ctx->stmt_get_kubernetes_event_exists_by_uid); + sqlite3_finalize(ctx->stmt_insert_kubernetes_event); + sqlite3_finalize(ctx->stmt_delete_old_kubernetes_events); flb_kubernetes_event_db_close(ctx->db); } #endif From 2c6b20703ac26131ec7c97b1f8e4ec4ff1a7e650 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 4 Aug 2026 08:46:44 -0600 Subject: [PATCH 6/8] tests: integration: cover kubernetes events database Signed-off-by: Eduardo Silva --- .../tests/test_in_kubernetes_events_001.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/integration/scenarios/in_kubernetes_events/tests/test_in_kubernetes_events_001.py b/tests/integration/scenarios/in_kubernetes_events/tests/test_in_kubernetes_events_001.py index 3a258cf5fde..2b6f29efcb6 100644 --- a/tests/integration/scenarios/in_kubernetes_events/tests/test_in_kubernetes_events_001.py +++ b/tests/integration/scenarios/in_kubernetes_events/tests/test_in_kubernetes_events_001.py @@ -117,6 +117,7 @@ def _run_kube_api_server(): def _write_config(tmp_path, kube_api_port): token_file = tmp_path / "token" token_file.write_text("test-token", encoding="utf-8") + database_file = tmp_path / "kubernetes-events.db" config_file = tmp_path / "kubernetes_events_watch_timeout.conf" config_file.write_text( "\n".join( @@ -124,7 +125,7 @@ def _write_config(tmp_path, kube_api_port): "[SERVICE]", " Flush 1", " Grace 1", - " Log_Level info", + " Log_Level debug", " HTTP_Server On", " HTTP_Port ${FLUENT_BIT_HTTP_MONITORING_PORT}", "", @@ -132,6 +133,7 @@ def _write_config(tmp_path, kube_api_port): " Name kubernetes_events", f" Kube_URL http://127.0.0.1:{kube_api_port}", f" Kube_Token_File {token_file}", + f" Db {database_file}", " tls Off", " Interval_Sec 5", " Interval_NSec 0", @@ -173,5 +175,8 @@ def test_kubernetes_events_reconnects_stalled_watch(tmp_path): assert kube_api_server.list_requests >= 2 assert kube_api_server.watch_requests >= 2 assert all("timeoutSeconds=1" in path for path in kube_api_server.watch_paths) - assert log_text.count(EVENT_UID) == 1 - assert log_text.count(RECOVERED_EVENT_UID) == 1 + assert log_text.count(f'"uid"=>"{EVENT_UID}"') == 1 + assert log_text.count(f'"uid"=>"{RECOVERED_EVENT_UID}"') == 1 + assert "unable to find metadata to save event" not in log_text + assert f"inserted k8s event: uid={EVENT_UID}" in log_text + assert f"inserted k8s event: uid={RECOVERED_EVENT_UID}" in log_text From e18cb1fc8f090fbe9cfe584da1a60e2f4e144be2 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 4 Aug 2026 09:59:06 -0600 Subject: [PATCH 7/8] in_kubernetes_events: reject missing string fields Signed-off-by: Eduardo Silva --- .../in_kubernetes_events/kubernetes_events.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/plugins/in_kubernetes_events/kubernetes_events.c b/plugins/in_kubernetes_events/kubernetes_events.c index da4c9010d03..291d90d020f 100644 --- a/plugins/in_kubernetes_events/kubernetes_events.c +++ b/plugins/in_kubernetes_events/kubernetes_events.c @@ -217,6 +217,8 @@ static int record_get_field_sds(msgpack_object *obj, const char *fieldname, flb_ { msgpack_object *v; + *val = NULL; + v = record_get_field_ptr(obj, fieldname); if (v == NULL) { return 0; @@ -226,6 +228,10 @@ static int record_get_field_sds(msgpack_object *obj, const char *fieldname, flb_ } *val = flb_sds_create_len(v->via.str.ptr, v->via.str.size); + if (*val == NULL) { + return -1; + } + return 0; } @@ -361,7 +367,7 @@ static bool check_event_is_filtered(struct k8s_events *ctx, msgpack_object *obj, int ret; uint64_t outdated; msgpack_object *metadata; - flb_sds_t uid; + flb_sds_t uid = NULL; uint64_t resource_version; outdated = cfl_time_now() - ((uint64_t) ctx->retention_time * 1000000000ULL); @@ -384,8 +390,8 @@ static bool check_event_is_filtered(struct k8s_events *ctx, msgpack_object *obj, } ret = record_get_field_sds(metadata, "uid", &uid); - if (ret == -1) { - flb_plg_error(ctx->ins, "Cannot get resourceVersion for item in response"); + if (ret == -1 || uid == NULL) { + flb_plg_error(ctx->ins, "Cannot get uid for item in response"); return FLB_FALSE; } @@ -526,8 +532,9 @@ static int process_watched_event(struct k8s_events *ctx, char *buf_data, size_t } ret = record_get_field_sds(&root, "type", &event_type); - if (ret == -1) { + if (ret == -1 || event_type == NULL) { flb_plg_warn(ctx->ins, "Streamed Event 'type' not found"); + ret = -1; goto msg_error; } @@ -739,7 +746,7 @@ static int k8s_events_sql_insert_event(struct k8s_events *ctx, msgpack_object *i uint64_t resource_version; struct flb_time last; msgpack_object *meta; - flb_sds_t uid; + flb_sds_t uid = NULL; meta = record_get_field_ptr(item, "metadata"); @@ -755,7 +762,7 @@ static int k8s_events_sql_insert_event(struct k8s_events *ctx, msgpack_object *i } ret = record_get_field_sds(meta, "uid", &uid); - if (ret == -1) { + if (ret == -1 || uid == NULL) { flb_plg_error(ctx->ins, "unable to find uid in metadata to save event"); return -1; } From a6bf1db67a326e5279c6b34716859c8794c1401c Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 4 Aug 2026 09:59:06 -0600 Subject: [PATCH 8/8] tests: integration: cover missing event fields Signed-off-by: Eduardo Silva --- .../tests/test_in_kubernetes_events_001.py | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/integration/scenarios/in_kubernetes_events/tests/test_in_kubernetes_events_001.py b/tests/integration/scenarios/in_kubernetes_events/tests/test_in_kubernetes_events_001.py index 2b6f29efcb6..8d9a8fc0b3e 100644 --- a/tests/integration/scenarios/in_kubernetes_events/tests/test_in_kubernetes_events_001.py +++ b/tests/integration/scenarios/in_kubernetes_events/tests/test_in_kubernetes_events_001.py @@ -13,15 +13,17 @@ RECOVERED_EVENT_UID = "post-recovery-event-uid" -def _event(resource_version, uid): +def _event(resource_version, uid=None): timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) - return { + event = { "metadata": { "creationTimestamp": timestamp, "resourceVersion": str(resource_version), - "uid": uid, } } + if uid is not None: + event["metadata"]["uid"] = uid + return event class _KubeApiServer(http.server.ThreadingHTTPServer): @@ -36,6 +38,7 @@ def __init__(self, server_address, handler_class): self.watch_paths = [] self.event = _event(2, EVENT_UID) self.recovered_event = _event(3, RECOVERED_EVENT_UID) + self.uidless_event = _event(4) class _KubeApiHandler(http.server.BaseHTTPRequestHandler): @@ -54,10 +57,13 @@ def do_GET(self): self.end_headers() if watch_request <= 2: event = self.server.event + type_key = "type" + if watch_request == 1: + type_key = "typeExtra" if watch_request == 2: event = self.server.recovered_event payload = ( - json.dumps({"type": "ADDED", "object": event}) + "\n" + json.dumps({type_key: "ADDED", "object": event}) + "\n" ).encode("utf-8") self.wfile.write(f"{len(payload):x}\r\n".encode("ascii")) self.wfile.write(payload) @@ -85,7 +91,11 @@ def do_GET(self): "kind": "EventList", "apiVersion": "v1", "metadata": {"resourceVersion": str(min(list_request, 2))}, - "items": [self.server.event] if list_request > 1 else [], + "items": ( + [self.server.event, self.server.uidless_event] + if list_request > 1 + else [] + ), } ).encode("utf-8") self.send_response(200) @@ -177,6 +187,9 @@ def test_kubernetes_events_reconnects_stalled_watch(tmp_path): assert all("timeoutSeconds=1" in path for path in kube_api_server.watch_paths) assert log_text.count(f'"uid"=>"{EVENT_UID}"') == 1 assert log_text.count(f'"uid"=>"{RECOVERED_EVENT_UID}"') == 1 + assert "Streamed Event 'type' not found" in log_text + assert "Cannot get uid for item in response" in log_text + assert "unable to find uid in metadata to save event" in log_text assert "unable to find metadata to save event" not in log_text assert f"inserted k8s event: uid={EVENT_UID}" in log_text assert f"inserted k8s event: uid={RECOVERED_EVENT_UID}" in log_text