CVE-2026-50197 Overview
CVE-2026-50197 is an HTTP Request Smuggling class weakness [CWE-444] affecting Zalando Skipper, an HTTP router and reverse proxy used for service composition. Versions prior to 0.26.10 silently bypass request-body inspection in the OpenPolicyAgent (OPA) integration when clients send HTTP/1.1 requests with Transfer-Encoding: chunked or HTTP/2 requests that omit the content-length pseudo-header. The opaAuthorizeRequestWithBody filter and OpenPolicyAgentInstance.ExtractHttpBodyOptionally in filters/openpolicyagent/openpolicyagent.go produce an empty raw_body and input.parsed_body, while the upstream service still receives the full attacker-controlled body. Attackers can therefore evade OPA authorization decisions that rely on body content.
Critical Impact
Attackers can bypass OPA-based authorization policies by omitting or chunking the request body, allowing malicious payloads to reach upstream services without policy inspection.
Affected Products
- Zalando Skipper versions prior to 0.26.10
- Deployments using the opaAuthorizeRequestWithBody filter
- Services relying on OpenPolicyAgent request-body inspection for authorization
Discovery Timeline
- 2026-07-17 - CVE-2026-50197 published to NVD
- 2026-07-23 - Last updated in NVD database
Technical Details for CVE-2026-50197
Vulnerability Analysis
Skipper's OPA integration uses req.ContentLength to decide whether the request body should be buffered and passed to OPA for policy evaluation. Go's net/http sets req.ContentLength to -1 when the client uses Transfer-Encoding: chunked on HTTP/1.1 or omits the content-length pseudo-header on HTTP/2. The buffering routine fillBuffer short-circuits on its len < expectedSize predicate when passed this negative sentinel, returning an empty buffer. OPA then evaluates a policy against an empty raw_body and input.parsed_body, while Skipper forwards the full body to the upstream service unchanged. This inconsistency between authorization view and upstream view is the classic pattern captured by [CWE-444].
Root Cause
The root cause is an unchecked negative ContentLength sentinel. The code path assumed a non-negative body size and never mapped the -1 unknown-length case to the configured maxBodyBytes cap, causing the OPA input body to be silently truncated to zero bytes.
Attack Vector
A remote, unauthenticated attacker sends an HTTP/1.1 request with Transfer-Encoding: chunked or an HTTP/2 request without content-length, embedding a payload that would normally be rejected by an OPA policy inspecting request bodies (for example, disallowed fields, admin flags, or restricted JSON keys). The OPA filter observes an empty body, allows the request, and Skipper proxies the full payload to the backend.
func (opa *OpenPolicyAgentInstance) ExtractHttpBodyOptionally(req *http.Request) (io.ReadCloser, []byte, func(), error) {
body := req.Body
+ // `req.ContentLength == -1` is set by net/http when the client uses
+ // Transfer-Encoding: chunked (HTTP/1.1) or omits content-length in
+ // HTTP/2 framing. Treat unknown-length bodies as up-to-max-bytes and
+ // drive fillBuffer with the policy cap instead of the negative
+ // sentinel, otherwise the fillBuffer loop short-circuits on its
+ // `len < expectedSize` predicate and OPA evaluates an empty body.
+ expectedSize := req.ContentLength
+ if expectedSize < 0 {
+ expectedSize = opa.maxBodyBytes
+ }
+
if body != nil && !opa.EnvoyPluginConfig().SkipRequestBodyParse &&
- req.ContentLength <= int64(opa.maxBodyBytes) {
+ expectedSize <= int64(opa.maxBodyBytes) {
wrapper := newBufferedBodyReader(req.Body, opa.maxBodyBytes, opa.bodyReadBufferSize)
- requestedBodyBytes := bodyUpperBound(req.ContentLength, opa.maxBodyBytes)
+ requestedBodyBytes := bodyUpperBound(expectedSize, opa.maxBodyBytes)
if !opa.registry.maxMemoryBodyParsingSem.TryAcquire(requestedBodyBytes) {
return req.Body, nil, func() {}, ErrTotalBodyBytesExceeded
}
- rawBody, err := wrapper.fillBuffer(req.ContentLength)
+ rawBody, err := wrapper.fillBuffer(expectedSize)
return wrapper, rawBody, func() { opa.registry.maxMemoryBodyParsingSem.Release(requestedBodyBytes) }, err
}
Source: GitHub Commit 3152f3b (patch)
Detection Methods for CVE-2026-50197
Indicators of Compromise
- Inbound HTTP/1.1 requests carrying Transfer-Encoding: chunked toward Skipper routes protected by opaAuthorizeRequestWithBody.
- HTTP/2 requests to OPA-protected routes with no content-length pseudo-header but a non-empty DATA frame payload.
- OPA decision logs showing empty input.parsed_body or raw_body while upstream access logs show non-zero request body sizes for the same request ID.
Detection Strategies
- Correlate Skipper access logs with OPA decision logs on request ID and flag records where OPA saw a zero-byte body but the upstream received a non-zero body.
- Alert on any request reaching routes that use opaAuthorizeRequestWithBody when Transfer-Encoding: chunked is present or content-length is missing.
- Review OPA policies referencing input.parsed_body for evaluations that returned allow with empty body inputs.
Monitoring Recommendations
- Ship Skipper and OPA logs into a centralized analytics platform and build a saved query joining proxy and policy events by request ID.
- Baseline the ratio of chunked to content-length requests per route and alert on sudden increases against OPA-protected endpoints.
- Monitor upstream services for authorization-sensitive fields appearing in bodies that OPA policies should have blocked.
How to Mitigate CVE-2026-50197
Immediate Actions Required
- Upgrade Zalando Skipper to version 0.26.10 or later on all proxy instances.
- Inventory routes that invoke opaAuthorizeRequestWithBody and prioritize their upgrade.
- Review recent traffic to OPA-protected endpoints for chunked or missing-content-length requests that may indicate exploitation attempts.
Patch Information
The fix is delivered in Skipper v0.26.10 via Pull Request #4041 and commit 3152f3b. The patch treats a negative req.ContentLength as an unknown-length body and drives fillBuffer with the configured maxBodyBytes cap instead of the -1 sentinel. Full details are in the GitHub Security Advisory GHSA-659f-rgp5-w4wf.
Workarounds
- Terminate chunked encoding at an upstream layer that rewrites requests to include an explicit Content-Length header before they reach Skipper.
- Reject requests to OPA-protected routes when Transfer-Encoding: chunked is present or content-length is missing until the patch is deployed.
- Where feasible, enforce authorization decisions inside the upstream service on the fully received body as a defense-in-depth control.
# Example: block chunked and missing-content-length requests at an ingress
# proxy in front of Skipper until v0.26.10 is deployed.
# NGINX snippet
if ($http_transfer_encoding ~* chunked) { return 411; }
if ($content_length = "") { return 411; }
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

