GitHub Advisoryadvisory · Go
GHSA-5gpm-rgj3-9q76 Skipper has OPA body-authz bypass: truncated_body mitigation fails open on chunked/HTTP-2 (incomplete fix GHSA-8qqm-fp2q-v734)
HIGH
Official page ↗
- Aliases
- CVE-2026-86043
- Ecosystem
- Go
- Packages
- github.com/zalando/skipper
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
- Weaknesses
- CWE-863
- Reviewed
- 2026-09-17
Affected packages
• Go github.com/zalando/skipper: < 0.27.37 → fixed in 0.27.37
Description
• Affected component: `filters/openpolicyagent/openpolicyagent.go` → `ExtractHttpBodyOptionally`; combined with `github.com/open-policy-agent/opa-envoy-plugin` `envoyauth/request.go` → `getParsedBody` / `checkIfHTTPBodyTruncated`. Filter: `opaAuthorizeRequestWithBody`.
• Affected versions: ` truncated_body = false
}
func checkIfHTTPBodyTruncated(contentLength string, bodyLength int64) (bool, error) {
cl, _ := strconv.ParseInt(contentLength, 10, 64)
if cl != -1 && cl > bodyLength { return true, nil }
return false, nil
}
```
Truncation can only ever be signalled by comparing `content-length` against the received body length. With chunked/HTTP-2 there is no `content-length`, so the comparison is skipped and `truncated_body` is reported `false`. Skipper (`ExtractHttpBodyOptionally`) meanwhile *does* truncate the chunked body to `maxBodyBytes` (it sets `expectedSize = maxBodyBytes` when `req.ContentLength < 0`), producing the exact divergence: OPA is told "not truncated", but the body was truncated, and the backend receives the whole thing.
Reachability
1. Deployment runs `opaAuthorizeRequestWithBody` with a body-inspecting policy that follows the GHSA-8qqm mitigation (`allow if input.truncated_body == false`). This is the maintainer-recommended configuration (advisory + v0.27.26 docs).
2. Attacker sends a request whose body exceeds `max-request-body-size` using `Transfer-Encoding: chunked` (or HTTP/2). No `content-length` header is present.
3. `ExtractHttpBodyOptionally` reads/truncates the body to `maxBodyBytes`; `rawBody` is the truncated prefix.
4. `AdaptToExtAuthRequest` forwards the lowercased headers (no `content-length`) and the truncated `RawBody` to OPA.
5. `getParsedBody` finds no `content-length` → `truncated_body = false`; for a non-parsed content-type it also returns `parsed_body = null` with no error.
6. Policy: `input.truncated_body == false` is satisfied → `allow = true`.
7. Skipper forwards the request; `bufferedBodyReader.Read` serves the buffered prefix and then continues reading the original `req.Body`, delivering the full oversized payload to the upstream.
Every guard on the path is accounted for: the only "guard" is `truncated_body`, and it is defeated by omitting `content-length`.
Impact
Bypass of OPA request-body authorization for any deployment that adopted the official `truncated_body` mitigation. Requests that the policy is meant to reject (oversized / un-inspectable bodies, or bodies whose forbidden content lies beyond the inspection window) are authorized and forwarded in full to the protected upstream. Same security property and severity class as CVE-2026-50197 / GHSA-8qqm (both High).
CVSS 3.1
Vector: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N` = 7.5 High
• AV:N — remote HTTP request.
• AC:L — single crafted request; no race/special conditions (just chunked framing + padding).
• PR:N / UI:N — unauthenticated, no user interaction.
• S:U — impact within the authorized component/upstream trust scope.
• C:N — no direct disclosure by the bypass itself.
• I:H — authorization control is bypassed; forbidden request content reaches the upstream (integrity of the access-control decision / protected resource).
• A:N — not primarily an availability issue.
(Conditional: exploitable only where `opaAuthorizeRequestWithBody` is used with a `truncated_body`-gated policy — i.e. deployments that followed the GHSA-8qqm mitigation. Consistent with the conditional nature of the parent CVEs.)
Proof of Concept
Executable Go test added to the OPA filter package. It stands up a real Skipper proxy (`proxy.WithParams`) + a real OPA control plane (`opasdktest`) with `WithMaxRequestBodyBytes(32)` and the advisory's verbatim mitigation policy (`allow if input.truncated_body == false`), routed to a recording upstream.
• Positive control — oversized body with Content-Length → `truncated_body=true` → 403 (mitigation works).
• Bypass — identical oversized body sent chunked (no Content-Length) → `truncated_body=false` → 200, and the upstream receives the full 66-byte payload past the 32-byte inspection cap.
Command and observed benign output:
```
$ go test ./filters/openpolicyagent/ -run TestTruncatedBodyChunkedBypass -count=1 -v
poc_truncated_body_chunked_test.go:148: [content-length ] status=403 upstream_body_bytes=-1
poc_truncated_body_chunked_test.go:154: [chunked ] status=200 upstream_body_bytes=66
--- PASS: TestTruncatedBodyChunkedBypass (0.11s)
PASS
```
• content-length variant → 403, upstream received nothing (`-1`) — mitigation works.
• chunked variant (byte-identical body) → 200, upstream received the full 66-byte payload past the 32-byte cap — authorization bypass.
(Full PoC source is appended below by the submission tool via `--poc-file`.)
Adversarial re-read (refutation attempts)
• *"Truncated JSON just fails to parse → fail closed."* True for `application/json` when truncation lands mid-token, but the bypass does not rely on JSON: `application/x-www-form-urlencoded` parses leniently after truncation, and unparsed/absent content-types return `(nil, false, nil)` with no error. The load-bearing signal is `t…
Related
Alias · CVE (1)
- CVE-2026-86043 Skipper is an HTTP router and reverse proxy for service composition. vulnerability
Weakness · CWE (1)
- CWE-863 Incorrect Authorization weakness
References
- https://github.com/zalando/skipper/security/advisories/GHSA-5gpm-rgj3-9q76 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-86043 advisory
- https://github.com/zalando/skipper/commit/2cfceabaa6ff0af65b312dcb9bcbe84691b9d507 web
- https://github.com/zalando/skipper package
- https://github.com/zalando/skipper/releases/tag/v0.27.35 web
- https://github.com/zalando/skipper/releases/tag/v0.27.37 web
- https://github.com/zalando/skipper/tree/v0.27.35 web