Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
EEF-CVE-2026-104635
Summary
Uncontrolled Recursion vulnerability in Protobuf.JSON.Decode in elixir-protobuf protobuf allows an unauthenticated remote attacker to crash the decoding process via a deeply nested JSON document. Any application that decodes attacker-supplied JSON with Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, or Protobuf.JSON.from_decoded/3 into a schema that contains a self-referential or cyclic message type is affected.
In lib/protobuf/json/decode.ex, the embedded-message clause of decode_singular/3 recurses into internal_from_json_data/3 once per nesting level without incrementing or checking the decoder's depth counter. The depth guard increase_depth_and_maybe_throw/1 covers only the Google.Protobuf.ListValue and Google.Protobuf.Struct clauses, so the recursion_limit option has no effect on user-defined message types. Each nesting level allocates a stack frame and heap objects, and a sufficiently deep document exhausts the memory of the decoding process. Confidentiality and integrity are not affected.
This issue affects protobuf: from 0.8.0 before 0.17.1.
Details
1. Entry points. Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, and Protobuf.JSON.from_decoded/3 in lib/protobuf/json.ex validate options and call Protobuf.JSON.Decode.from_json_data/3, which builds a state map carrying depth: 0 and the recursion_limit (default 100). from_decoded/3 accepts already-parsed data, so the underlying JSON parser never bounds the depth either.
2. Unguarded recursion. For a user-defined message module, internal_from_json_data/3 calls decode_message/4, which calls decode_regular_fields/3 and decode_oneof_fields/3. Each field value passes through decode_value/3 and, for a singular embedded message, reaches the decode_singular/3 clause matching embedded?: true. That clause calls internal_from_json_data/3 with state unchanged, so state.depth stays at 0 at every nesting level. Repeated fields and map values reach the same clause through decode_repeated/3 and decode_map/3.
3. Guard coverage. increase_depth_and_maybe_throw/1 increments depth and throws {:recursion_limit_exceeded, limit} when it exceeds the limit. It is called only from the Google.Protobuf.ListValue and Google.Protobuf.Struct clauses, which the Google.Protobuf.Value clause delegates to. The recursion_limit documentation scopes the option to those wrappers, and no other path checks depth.
4. Result. A self-referential schema such as a Tree message with a Tree child field recurses once per JSON nesting level with no bound. Each level holds a live stack frame and allocates heap objects, so a sufficiently deep document exhausts the memory of the decoding process and crashes it. The BEAM max_heap_size process flag defaults to unlimited, so repeated or concurrent requests can exhaust the memory of the whole node.
Proof of concept
- Define and compile a self-referential proto3 message, for example a
TreeNodemessage with a single embedded fieldchildof typeTreeNode. - Build a JSON document that nests the
childfield several thousand levels deep, for example by wrapping{}in{"child": ...}5,000 times. - Decode it with
Protobuf.JSON.decode(json, TreeNode, recursion_limit: 100). - Observe that no
Protobuf.JSON.DecodeErrorfor an exceeded recursion limit is raised and the decoder walks every level. The same depth in aGoogle.Protobuf.Structpayload raises{:recursion_limit_exceeded, 100}. At larger depths the decoding process consumes hundreds of megabytes and crashes with stack and heap exhaustion.
Impact
An unauthenticated client that can reach an endpoint decoding JSON into a self-referential message type can crash the decoding process through stack and memory exhaustion with a single request. Repeated or concurrent requests can exhaust the memory of the whole node and disrupt co-located workloads.
Workarounds
Reject JSON documents whose nesting depth exceeds a fixed bound at the web or middleware layer before passing them to the decoder. No decoder option limits recursion for user-defined message types, so the check must happen before Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, or Protobuf.JSON.from_decoded/3 is called.
Configurations
The application decodes attacker-controlled JSON with Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, or Protobuf.JSON.from_decoded/3 into a message type whose schema contains a self-referential or cyclic embedded message.
Upload your own SBOM in CycloneDX 1.6 or higher (JSON) directly here to check your vulnerabilities.
Drag and drop some file here, or click to select
The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker does not need any special privileges or access rights. No user interaction is needed for the attacker to exploit this vulnerability.
Exploitation attempts have been detected. Elevated vigilance and prompt remediation are advised.
The exploit probability is very low. The vulnerability is unlikely to be exploited in the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Browse More
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard