Consul's event endpoint is vulnerable to denial of service
Description
Consul and Consul Enterprise’s (“Consul”) event endpoint is vulnerable to denial of service (DoS) due to lack of maximum value on the Content Length header. This vulnerability, CVE-2025-11375, is fixed in Consul Community Edition 1.22.0 and Consul Enterprise 1.22.0, 1.21.6, 1.20.8 and 1.18.12.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Consul event endpoint vulnerable to DoS via oversized Content-Length header; fixed in versions 1.22.0, 1.21.6, 1.20.8, 1.18.12.
Vulnerability
The event endpoint in Consul and Consul Enterprise lacked a maximum value check on the Content-Length header, allowing an attacker to send a request with an arbitrarily large Content-Length value. This could cause resource exhaustion and denial of service (DoS) [1][2]. The fix introduces a limit of 100 bytes, as documented in the Consul event command documentation [1].
Exploitation
An attacker with network access to the Consul HTTP API can exploit this vulnerability by sending a crafted HTTP request with an oversized Content-Length header. No authentication is required for the event endpoint, making it accessible to unauthenticated attackers. The server may allocate excessive resources or hang, leading to a denial of service [2].
Impact
Successful exploitation results in denial of service, rendering the Consul service unavailable. This can disrupt service discovery, health checking, and other critical functions within a Consul cluster, potentially affecting all dependent services [2].
Mitigation
The vulnerability is fixed in Consul Community Edition 1.22.0 and Consul Enterprise 1.22.0, 1.21.6, 1.20.8, and 1.18.12 [2][3]. Users should upgrade to these versions or apply the patch from the referenced commit [4]. No workarounds are currently available.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/hashicorp/consulGo | < 1.22.0 | 1.22.0 |
Affected products
4- Range: <=1.21.6||<=1.20.8||<=1.18.12
- HashiCorp/Consulv5Range: 0
- HashiCorp/Consul Enterprisev5Range: 0
Patches
1e794201d0c61fix: event endpoint content lenght limit (#22836)
3 files changed · +144 −4
agent/event_endpoint.go+22 −4 modified@@ -5,6 +5,7 @@ package agent import ( "bytes" + "fmt" "io" "net/http" "strconv" @@ -44,12 +45,29 @@ func (s *HTTPHandlers) EventFire(resp http.ResponseWriter, req *http.Request) (i } // Get the payload - if req.ContentLength > 0 { + if req.ContentLength >= 0 { + // The underlying gossip sets limits on the size of a user event + // message. It is hard to give an exact number, as it depends on various + // parameters of the event, but the payload should be kept very small + // (< 100 bytes). We've multiplied this by 3 to be safe. + const maxEventPayloadSize = 300 + if req.ContentLength > maxEventPayloadSize { + return nil, HTTPError{ + StatusCode: http.StatusRequestEntityTooLarge, + Reason: fmt.Sprintf("Event payload too large, received %d bytes, max size: %d bytes. User events should be kept small for efficient gossip propagation.", + req.ContentLength, maxEventPayloadSize), + } + } + var buf bytes.Buffer - if _, err := io.Copy(&buf, req.Body); err != nil { - return nil, err + if req.Body != nil { + if _, err := io.Copy(&buf, req.Body); err != nil { + return nil, err + } + event.Payload = buf.Bytes() } - event.Payload = buf.Bytes() + } else { + return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Event payload size must be greater than zero"} } // Try to fire the event
agent/event_endpoint_test.go+119 −0 modified@@ -379,6 +379,125 @@ func TestEventList_EventBufOrder(t *testing.T) { }) } +func TestEventFire_PayloadSizeLimit(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + const maxPayloadSize = 300 + + type expectedResponse struct { + success bool + statusCode int + errorMessage string + eventName string + payloadSize int + } + + testCases := []struct { + name string + payloadSize int + expectedResponse *expectedResponse + description string + }{ + { + name: "empty payload", + payloadSize: 0, + expectedResponse: &expectedResponse{ + success: true, + eventName: "test", + payloadSize: 0, + }, + description: "empty payload should be accepted", + }, + { + name: "payload within limit", + payloadSize: 50, + expectedResponse: &expectedResponse{ + success: true, + eventName: "test", + payloadSize: 50, + }, + description: "small payload should be accepted", + }, + { + name: "payload at exact limit", + payloadSize: maxPayloadSize, + expectedResponse: &expectedResponse{ + success: true, + eventName: "test", + payloadSize: maxPayloadSize, + }, + description: "payload at exactly 300 bytes should be accepted", + }, + { + name: "payload exceeds limit by 1 byte", + payloadSize: maxPayloadSize + 1, + expectedResponse: &expectedResponse{ + statusCode: http.StatusRequestEntityTooLarge, + errorMessage: "Event payload too large", + }, + description: "payload exceeding limit should be rejected", + }, + { + name: "large payload", + payloadSize: 500, + expectedResponse: &expectedResponse{ + statusCode: http.StatusRequestEntityTooLarge, + errorMessage: "Event payload too large", + }, + description: "large payload should be rejected", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var payload []byte + if tc.payloadSize <= 0 { + payload = []byte{} + } else { + payload = bytes.Repeat([]byte("x"), tc.payloadSize) + } + + url := "/v1/event/fire/test" + req, err := http.NewRequest("PUT", url, bytes.NewBuffer(payload)) + require.NoError(t, err) + + resp := httptest.NewRecorder() + obj, err := a.srv.EventFire(resp, req) + + if tc.expectedResponse.success { + require.NoError(t, err, tc.description) + require.NotNil(t, obj, "Should return event object on success") + + event, ok := obj.(*UserEvent) + require.True(t, ok, "Expected *UserEvent, got %T", obj) + require.Equal(t, tc.expectedResponse.eventName, event.Name) + + if tc.expectedResponse.payloadSize == 0 { + // Empty payload should result in nil + require.Nil(t, event.Payload) + } else { + expectedPayload := bytes.Repeat([]byte("x"), tc.expectedResponse.payloadSize) + require.Equal(t, expectedPayload, event.Payload) + } + } else { + require.Error(t, err, tc.description) + httpErr, ok := err.(HTTPError) + require.True(t, ok, "Expected HTTPError, got %T", err) + require.Equal(t, tc.expectedResponse.statusCode, httpErr.StatusCode) + require.Contains(t, httpErr.Reason, tc.expectedResponse.errorMessage) + require.Nil(t, obj, "Should not return event object on error") + } + }) + } +} + func TestUUIDToUint64(t *testing.T) { t.Parallel() inp := "cb9a81ad-fff6-52ac-92a7-5f70687805ec"
.changelog/22836.txt+3 −0 added@@ -0,0 +1,3 @@ +```release-note:security +security: adding a maximum Content-Length on the event endpoint to fix denial-of-service (DoS) attacks. This resolves [CVE-2025-11375](https://nvd.nist.gov/vuln/detail/CVE-2025-11375). +```
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-qh7p-pfq3-677hghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-11375ghsaADVISORY
- discuss.hashicorp.com/t/hcsec-2025-28-consuls-event-endpoint-is-vulnerable-to-denial-of-service/76723ghsaWEB
- github.com/hashicorp/consul/commit/e794201d0c618333d81ad775270f7b32801178fbghsaWEB
- github.com/hashicorp/consul/pull/22836ghsaWEB
- github.com/hashicorp/consul/releases/tag/v1.22.0ghsaWEB
- pkg.go.dev/vuln/GO-2025-4082ghsaWEB
News mentions
0No linked articles in our index yet.