Endor Labs AI SAST Finds Two Apache Kafka Zero-Days: Unbounded Memory Allocation from Brokers to Kafka Streams | Endor Labs
Endor Labs AI SAST found three unbounded memory allocation flaws in Apache Kafka, fixed under CVE-2026-114349 (DefaultRecord decoder, a ~90-byte compressed batch that kills consumers, brokers and the log cleaner) and CVE-2026-111551 (ListDeserializer and Kafka Streams AssignmentInfo decoding). The post gives code, reproducers and payload bytes. Remediation: upgrade to 4.2.2, 4.3.2 or 4.4.0, set max.decompressed.message.bytes, restrict write and group ACLs, and alert on DeadThreadCount.
What it is
Two Apache Kafka CVEs, both rated important, found by Endor Labs' AI SAST in a single scan on May 1, 2026. Both are unbounded allocations: code reads a size or count from the input and allocates before checking it against the bytes present. The Kafka project published both advisories on October 28, 2026.
- CVE-2026-114349:
DefaultRecord.readFrom(InputStream overload, line 286) allocates a record body sized by a varint inside the compressed data. A batch of about 90 bytes can declare roughly 2 GiB. - CVE-2026-111551:
ListDeserializer(new ArrayList<>(nullIndexListSize), plus a second site at line 179) and Kafka StreamsAssignmentInfodecoders (decodeActiveTasksand four siblings).
How it works
Record size limits, max.message.bytes and quotas all measure the compressed or wire bytes, so none of them bounds what the payload declares. The producer computes the batch CRC, so the crafted batch is CRC-valid.
flowchart TD
A[Crafted ~90-byte batch produced] --> B{Compression matches topic?}
B -- Yes --> C[Stored and replicated unchanged]
B -- No --> D[Broker re-encodes: handler OOM]
C --> E[Consumers OOM on every fetch]
C --> F[Log cleaner thread dies]
D --> G[Broker JVM exits if ExitOnOutOfMemoryError]- Cleaner: the OutOfMemoryError escapes error handling and the thread is not restarted. With the default single cleaner thread, compaction stops on all compacted topics, including
__consumer_offsetsand__transaction_state. The broker still passes health checks. - ListDeserializer: a 9-byte value from a stock
KafkaProducer,00 7F FF FF FF 00 00 00 00, crashes every consumer usingListDeserializeron its next poll and after each restart. - Streams assignment: a plain consumer can register an assignor named
stream, join a Streams app's group, win leader election and return arbitrary assignment bytes. The 12 bytes00 00 00 0B 00 00 00 0B 7F FF FF FFcrashed every stream thread, under both eager and cooperative rebalancing. The attacker stays in the group, so each restart crashes again.
Preconditions / Affected
- CVE-2026-114349: Write access to a topic. Cleaner impact needs compacted topics. Broker JVM exit needs mismatched compression plus
-XX:+ExitOnOutOfMemoryError. - ListDeserializer: write access to topics read with
ListDeserializerorListSerde, including Streams pipelines, connectors and wrappers. - Streams assignment: only the
Group:ReadACL on the group. The post says this matters most in multi-tenant clusters with wildcard group ACLs. - The fix also covers four more Streams paths:
ProcessorRecordContext,ChangedDeserializer,CombinedKeySchemaandProcessorMetadata. These read internal changelog and repartition topics and committed offset metadata, so write access there counts as attack surface. - Fixed in 4.2.2, 4.3.2 and 4.4.0.
What to do
- Upgrade to 4.2.2, 4.3.2 or 4.4.0.
- Set
max.decompressed.message.bytesat broker or topic level. Upgrading alone does not protect against CVE-2026-114349, because the default stays near 2 GiB for compatibility. Pick a value at or slightly above your largest legitimate decompressed record, and well below broker heap divided by request-handler plus cleaner threads. - Until upgraded, limit Write access to trusted producers. Restrict each Streams app's internal topics and consumer group to its own principal, and restrict who can produce to
ListSerdetopics. - Alert on this metric:
kafka.log:type=LogCleaner,name=DeadThreadCount > 0Caveats
- Quotas and
max.message.bytesdo not mitigate either issue. - A crafted record already in a topic stays a hazard for consumers until it is removed or ages out, even after the limit is set.
- The author reproduced the attacks against
apache/kafka:4.2.0. Reproducer code is described, not published in full.
Organisations named here
- DefaultRecord
- ListDeserializer
- max.decompressed.message.bytes
- OutOfMemoryError
CVEs named here
- CVE-2026-114349
- CVE-2026-111551
Technologies named here
- Apache Kafka
- Custom raw Produce request reproducer
- JMX metric kafka.log:type=LogCleaner,name=DeadThreadCount
- Java
- Endor Labs
- Kafka Streams
Techniques named here
- Disclosure timeline
- Denial of service
Related in AppSec
Flowise MCP Security Bypass Enables RCE
Inside an Oracle Database SQL Injection Attack | Huntress
OASIS
