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…
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 · 8CVE-2026-71312CVE-2026-71312: rclone SFTP PowerShell Smart-Quote Filename OS Command Injection
- high · 8.8CVE-2026-59733CVE-2026-59733: rclone serve restic --private-repos Authorization Bypass via Path Traversal
- highCVE-2026-71309CVE-2026-71309: rclone serve restic Path Traversal Backend Root Escape