GHSA-8QQM-FP2Q-V734
Vulnerability from github – Published: 2026-07-17 21:49 – Updated: 2026-07-17 21:49Summary
A wrong policy can be an open door.
You have to check input.attributes.request.http.truncated_body in your policy.
Description
Incomplete fix for CVE-2026-50197: an oversized declared-Content-Length body still hands OPA an empty parsed_body, so deny-on-presence Rego policies fail OPEN while the full payload reaches upstream.
The CVE-2026-50197 fix (commit 3152f3b0, PR #4041, v0.26.10) substituted expectedSize = maxBodyBytes
only when req.ContentLength < 0 (chunked / HTTP/2 without content-length). But when a request declares a
Content-Length larger than maxBodyBytes, expectedSize > maxBodyBytes, the body-extraction if is
skipped entirely, and ExtractHttpBodyOptionally returns rawBodyBytes = nil — so OPA evaluates an empty
parsed_body, while the full forbidden payload still flows to the upstream. A deny-on-presence policy
(default allow = true; allow = false if input.parsed_body.<forbidden>) — the exact Rego shape the
advisory describes — fails OPEN. The fix's own comment reasons only about ContentLength == -1; the
oversized branch was never considered, and the added PoC test only covers small bodies.
Affected code
filters/openpolicyagent/openpolicyagent.goExtractHttpBodyOptionally: theexpectedSize <= maxBodyBytesgate lets an oversized declared body fall through toreturn req.Body, nil, func() {}, nil(OPA sees an empty document).- Corroborated by Skipper's own unit test "Read body exhausting max bytes" (
{ "welcome": "world" },maxBodySize: 5→bodyInPolicy: "").
Steps to reproduce
See attached docker-compose.yml (official golang image) + setup.sh + exploit.sh, which run a real
Skipper proxy (proxytest) with a real OPA control plane (opasdktest),
WithMaxRequestBodyBytes(32), policy allow = false if input.parsed_body.action == "delete", route
* -> opaAuthorizeRequestWithBody("test") -> upstream:
- {"action":"delete"} (19B ≤ 32) → 403 (denied).
- {"action":"delete","pad":"X..64"} (> 32) → 200, upstream received the full body (BYPASS).
- small chunked {"action":"delete"} → 403 (positive control: the original CVE is fixed).
(Library-tier: validated via Skipper's real proxy test harness, not a deploy of the official image; benign oracle = status diff + upstream-received body; no RCE.)
Impact
Deployments authorizing on request-body content via opaAuthorizeRequestWithBody + deny-on-presence Rego
can be bypassed by inflating the request body past -open-policy-agent-max-request-body-size (default
1 MB); the full payload still reaches the upstream.
Mitigation
Document how policy owners should block requests with oversized body.
Example deny by default and use "allow if" no oversized body:
default allow := false
allow if {
input.attributes.request.http.truncated_body == false
# ... body-based conditions
}
Example allow by default and use "deny if" an oversized body:
default deny := false
deny if {
input.attributes.request.http.truncated_body == true
# ... body-based conditions
}
Documentation is published by https://github.com/zalando/skipper/releases/tag/v0.27.26
Credit
Reported as part of an incomplete-patch measurement study (responsible disclosure).
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/zalando/skipper"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.27.26"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [],
"github_reviewed": true,
"github_reviewed_at": "2026-07-17T21:49:48Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\n\nA wrong policy can be an open door. \nYou have to check `input.attributes.request.http.truncated_body` in your policy.\n\n### Description\n\nIncomplete fix for CVE-2026-50197: an oversized declared-`Content-Length` body still hands OPA an empty `parsed_body`, so deny-on-presence Rego policies fail OPEN while the full payload reaches upstream.\n\nThe CVE-2026-50197 fix (commit `3152f3b0`, PR #4041, v0.26.10) substituted `expectedSize = maxBodyBytes`\nonly when `req.ContentLength \u003c 0` (chunked / HTTP/2 without content-length). But when a request declares a\n`Content-Length` larger than `maxBodyBytes`, `expectedSize \u003e maxBodyBytes`, the body-extraction `if` is\nskipped entirely, and `ExtractHttpBodyOptionally` returns `rawBodyBytes = nil` \u2014 so OPA evaluates an empty\n`parsed_body`, while the full forbidden payload still flows to the upstream. A deny-on-presence policy\n(`default allow = true; allow = false if input.parsed_body.\u003cforbidden\u003e`) \u2014 the exact Rego shape the\nadvisory describes \u2014 fails OPEN. The fix\u0027s own comment reasons only about `ContentLength == -1`; the\noversized branch was never considered, and the added PoC test only covers small bodies.\n\n### Affected code\n\n- `filters/openpolicyagent/openpolicyagent.go` `ExtractHttpBodyOptionally`: the\n `expectedSize \u003c= maxBodyBytes` gate lets an oversized declared body fall through to\n `return req.Body, nil, func() {}, nil` (OPA sees an empty document).\n- Corroborated by Skipper\u0027s own unit test \"Read body exhausting max bytes\" (`{ \"welcome\": \"world\" }`,\n `maxBodySize: 5` \u2192 `bodyInPolicy: \"\"`).\n\n### Steps to reproduce\n\nSee attached `docker-compose.yml` (official `golang` image) + `setup.sh` + `exploit.sh`, which run a real\nSkipper proxy (`proxytest`) with a real OPA control plane (`opasdktest`),\n`WithMaxRequestBodyBytes(32)`, policy `allow = false if input.parsed_body.action == \"delete\"`, route\n`* -\u003e opaAuthorizeRequestWithBody(\"test\") -\u003e upstream`:\n- `{\"action\":\"delete\"}` (19B \u2264 32) \u2192 **403** (denied).\n- `{\"action\":\"delete\",\"pad\":\"X..64\"}` (\u003e 32) \u2192 **200**, upstream received the full body (BYPASS).\n- small chunked `{\"action\":\"delete\"}` \u2192 **403** (positive control: the original CVE is fixed).\n\n(Library-tier: validated via Skipper\u0027s real proxy test harness, not a deploy of the official image; benign\noracle = status diff + upstream-received body; no RCE.)\n\n### Impact\n\nDeployments authorizing on request-body content via `opaAuthorizeRequestWithBody` + deny-on-presence Rego\ncan be bypassed by inflating the request body past `-open-policy-agent-max-request-body-size` (default\n1 MB); the full payload still reaches the upstream.\n\n### Mitigation\n\nDocument how policy owners should block requests with oversized body.\n\nExample deny by default and use \"allow if\" no oversized body:\n```rego\ndefault allow := false\n\nallow if {\n input.attributes.request.http.truncated_body == false\n # ... body-based conditions\n}\n```\n\nExample allow by default and use \"deny if\" an oversized body:\n```rego\ndefault deny := false\n\ndeny if {\n input.attributes.request.http.truncated_body == true\n # ... body-based conditions\n}\n```\n\nDocumentation is published by https://github.com/zalando/skipper/releases/tag/v0.27.26\n\n### Credit\n\nReported as part of an incomplete-patch measurement study (responsible disclosure).",
"id": "GHSA-8qqm-fp2q-v734",
"modified": "2026-07-17T21:49:48Z",
"published": "2026-07-17T21:49:48Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/zalando/skipper/security/advisories/GHSA-8qqm-fp2q-v734"
},
{
"type": "PACKAGE",
"url": "https://github.com/zalando/skipper"
},
{
"type": "WEB",
"url": "https://github.com/zalando/skipper/releases/tag/v0.27.26"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Skipper: Incomplete fix for CVE-2026-50197: an oversized body can bypass OPA deny-on-presence Rego policies"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.