🛡️ CVE-2026-50197 — skipper
Description
Skipper: opaAuthorizeRequestWithBody filter bypasses OPA policy on Transfer-Encoding — chunked / HTTP/2 requests
Summary
zalando/skipper's OpenPolicyAgent integration silently bypasses request-body
inspection on HTTP/1.1 Transfer-Encoding: chunked and HTTP/2 requests that
omit the content-length pseudo-header. When the
opaAuthorizeRequestWithBody filter is configured, the
OpenPolicyAgentInstance.ExtractHttpBodyOptionally helper produces an
empty raw_body for any request whose Content-Length header is missing,
while the underlying chunked body still flows through to the upstream
service. Rego policies that gate on input.parsed_body (e.g. "deny when a
forbidden field is present") evaluate against an empty document, treat the
forbidden field as absent, and authorize the request. The upstream handler
then receives the full attacker payload that the policy intended to block.
Affected versions
github.com/zalando/skipper versions <= v0.26.8 (the latest release on
2026-05-26, current master 4eed47ff). The vulnerable helper and gate
have lived in filters/openpolicyagent/openpolicyagent.go since the
buffered-body extractor was introduced; no released version contains the
fix at the time of filing.
Privilege required
Unauthenticated network access to the skipper proxy listener. The threat
model targets operators who place skipper in front of a private upstream
and rely on opaAuthorizeRequestWithBody to enforce body-content checks
(field allow/deny lists, payload schema gates, content-moderation flags,
multi-tenant per-action authorization). Both HTTP/1.1 and HTTP/2 clients
are affected; HTTP/2 traffic without a content-length pseudo-header is
the dominant case because Go's net/http sets
http.Request.ContentLength = -1 for chunked HTTP/1.1 AND for HTTP/2
requests whose framing carries the body as DATA frames without an explicit
length header.
Root cause
filters/openpolicyagent/openpolicyagent.go:1242-1269 (HEAD 4eed47ff):
```go
func bodyUpperBound(contentLength, maxBodyBytes int64) int64 {
if contentLength <= 0 {
return maxBodyBytes
}
if contentLength < maxBodyBytes {
return contentLength
}
return maxBodyBytes
}
func (opa *OpenPolicyAgentInstance) ExtractHttpBodyOptionally(req *http.Request) (io.ReadCloser, []byte, func(), error) {
body := req.Body
if body != nil && !opa.EnvoyPluginConfig().SkipRequestBodyParse &&
req.ContentLength <= int64(opa.maxBodyBytes) {
wrapper := newBufferedBodyReader(req.Body, opa.maxBodyBytes, opa.bodyReadBufferSize)
requestedBodyBytes := bodyUpperBound(req.ContentLength, opa.maxBodyBytes)
if !opa.registry.maxMemoryBodyParsingSem.TryAcquire(requestedBodyBytes) {
return req.Body, nil, func() {}, ErrTotalBodyBytesExceeded
}
rawBody, err := wrapper.fillBuffer(req.ContentLength)
return wrapper, rawBody, func() { opa.registry.maxMemoryBodyParsingSem.Release(requestedBodyBytes) }, err
}
return req.Body, nil, func() {}, nil
}
```
filters/openpolicyagent/openpolicyagent.go:1195-1210:
```go
func (m *bufferedBodyReader) fillBuffer(expectedSize int64) ([]byte, error) {
var err error
for err == nil && int64(m.bodyBuffer.Len()) < m.maxBufferSize && int64(m.bodyBuffer.Len()) < expectedSize {
var n int
n, err = m.input.Read(m.readBuffer)
m.bodyBuffer.Write(m.readBuffer[:n])
}
if err == io.EOF { err = nil }
return m.bodyBuffer.Bytes(), err
}
```
When the client sends Transfer-Encoding: chunked (HTTP/1.1) or an
HTTP/2 request without content-length, Go's net/http server sets
req.ContentLength = -1. The gate at line 1258 (`req.ContentLength <=
int64(opa.maxBodyBytes)) is true (-1 <= positiveLimit`), so the body
gets wrapped in bufferedBodyReader. bodyUpperBound(-1, max) returns
max, so the memory semaphore is acquired, but fillBuffer(-1) then
evaluates int64(m.bodyBuffer.Len()) < expectedSize as 0 < -1, which
is false on the first iteration. The loop never enters, the buffer stays
empty, and the helper returns []byte{} as rawBody to the caller.
The caller in
filters/openpolicyagent/opaauthorizerequest/opaauthorizerequest.go:121
hands this empty slice to envoy.AdaptToExtAuthRequest which puts it
into AttributeContext.Request.Http.RawBody. The OPA SDK then exposes
the empty buffer as both input.attributes.request.http.raw_body and
the parsed input.parsed_body document (the latter becomes an
empty/undefined value). Any Rego rule that asserts the presence of a
forbidden field in input.parsed_body evaluates to undefined and fails
into the rule's default (typically allow).
Meanwhile, the wrapped body returned to the filter (req.Body = body
at line 127 of opaauthorizerequest.go) is a bufferedBodyReader whose
Read() falls through to the underlying m.input.Read(p) when the
buffer is empty (lines 1212-1228). The upstream handler therefore
How this vulnerability can be exploited
This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality high, integrity high, availability none.
Weakness class
CVE-2026-50197 is classified as CWE-444: HTTP Request Smuggling. A proxy and a server disagree on where one request ends, letting an attacker slip a second request past the front end.
Affected software
CVE-2026-50197 is recorded against 2 packages.
- github.com/zalando/skipper
- unknown
Timeline and source
Published on 8 July 2026 and last revised on 21 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.
References
Details
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N/E:U/RL:O
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| github.com/zalando/skipper | — | — |
| unknown | — | — |
References
Similar Threats
- Unknown GO-2026-6019
- Unknown CVE-2026-54246
- Unknown CVE-2026-54247
- High GHSA-8qqm-fp2q-v734
- High CVE-2026-24470
More CVE 2026 advisories
Browse all of CVE 2026 in the advisory index.
Site Security Check
Is skipper part of your stack?
CVE-2026-50197 is rated CVSS 8.0 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.
Scan My Site Free →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.