Skipper opaAuthorizeRequestWithBody OPA Policy Bypass via Oversized Content-Length
Sending a JSON body larger than Skipper's configured OPA body-size limit causes the policy engine to evaluate an empty document, so any deny-on-presence Rego rule silently passes the request through t
The problem
Skipper's `opaAuthorizeRequestWithBody` filter calls `ExtractHttpBodyOptionally` to read the request body before passing it to OPA. The prior fix for CVE-2026-50197 only capped `expectedSize` when `Content-Length` was -1 (chunked / HTTP-2). When a client declares a `Content-Length` larger than `maxBodyBytes`, the `expectedSize <= maxBodyBytes` guard is false, the read is skipped entirely, and `rawBodyBytes` is nil.
OPA receives an empty `parsed_body`. A deny-on-presence Rego policy (`allow = false if input.parsed_body.action == "delete"`) sees no `action` field, defaults to `allow = true`, and forwards the full payload to the upstream. The bypass works silently with no error logged.
Proof of concept
A working proof-of-concept for this issue in github.com/zalando/skipper, with the exact payload below.
# Policy under test (default allow, deny-on-presence):
# default allow = true
# allow = false if input.parsed_body.action == "delete"
#
# Skipper configured with: WithMaxRequestBodyBytes(32)
#
# 19 bytes - BLOCKED (body fits within limit, OPA sees the field):
curl -X POST http://skipper-proxy/api \
-H 'Content-Type: application/json' \
-d '{"action":"delete"}'
# => 403 Forbidden
#
# >32 bytes - BYPASS (oversized, OPA sees empty parsed_body, policy passes):
curl -X POST http://skipper-proxy/api \
-H 'Content-Type: application/json' \
-d '{"action":"delete","pad":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}'
# => 200 OK (upstream received the full forbidden payload)The root cause is an off-by-one-branch error in `ExtractHttpBodyOptionally` (`filters/openpolicyagent/openpolicyagent.go`). The CVE-2026-50197 fix only substituted `expectedSize = maxBodyBytes` when `req.ContentLength < 0`. A request with a declared `Content-Length` exceeding `maxBodyBytes` bypasses that branch entirely, and the function returns `req.Body, nil, func(){}, nil` with `rawBodyBytes = nil`.
The patch in v0.27.26 adds a `truncated_body` boolean to the OPA input document (set `true` whenever the body was not fully read), letting policy authors explicitly deny truncated requests. The CWE is CWE-285 (Improper Authorization) compounded by an incomplete remediation pattern.
The fix
Upgrade to `github.com/zalando/skipper` v0.27.26. After upgrading, update every Rego policy that gates on body content to also check `input.attributes.request.http.truncated_body == false` (deny-by-default pattern) or `input.attributes.request.http.truncated_body == true` (allow-by-default pattern) so that oversized bodies are never silently authorized.
See the v0.27.26 release notes for updated policy examples.
Reported by Reported as part of an incomplete-patch measurement study (responsible disclosure).
Related research
- high · 8.7CVE-2026-50197CVE-2026-50197: Skipper opaAuthorizeRequestWithBody OPA Policy Bypass via Chunked Encoding
- high · 8.2CVE-2026-27771CVE-2026-27771: Gitea Container Registry Authentication Bypass
- high · 7.4CVE-2026-53714CVE-2026-53714: Envoy Gateway xDS Authentication Bypass in GatewayNamespaceMode
- high · 7.5CVE-2026-50285CVE-2026-50285: Pomerium Pre-Auth Denial of Service via HPKE Decompression Bomb