Skip to content

Opa gzip fix - #4357

Merged
MustafaSaber merged 8 commits into
masterfrom
opa-gzip-fix
Oct 20, 2026
Merged

MustafaSaber merged 8 commits into
masterfrom
opa-gzip-fix

Conversation

@MustafaSaber

Copy link
Copy Markdown
Collaborator

to be done

nuwandi-wickramasinghe_zse and others added 6 commits October 20, 2026 15:46
Decompress Content-Encoding: gzip request bodies in ExtractHttpBodyOptionally
before passing them to OPA, preventing policy bypass where input.parsed_body
was null for compressed bodies. Also fixes semaphore over-reservation by
releasing unused bytes immediately after reading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Mustafa Abdelrahman <mustafa.abdelrahman@zalando.de>
…pressing

Replace the gzip-decompression approach with an early rejection: when body
parsing is enabled (opaAuthorizeRequestWithBody, opaServeResponseWithReqBody),
any request carrying a Content-Encoding header is rejected with 415 Unsupported
Media Type before body extraction. This is simpler and more conservative — OPA
always receives a plain, parseable body or the request never reaches policy
evaluation.

The fix applies to all encodings, not only gzip, and covers both filter
implementations that call ExtractHttpBodyOptionally with bodyParsing enabled.
Requests without body parsing (opaAuthorizeRequest, opaServeResponse) are
unaffected.

The decompression logic is removed from ExtractHttpBodyOptionally. The semaphore
over-reservation fix (release unused bytes after read) is kept as it is correct
independent of encoding.

Regression tests added for both opaauthorizerequest and opaserveresponse,
verifying 415 for gzip/br/deflate encoded bodies with body parsing enabled,
and pass-through for encoded bodies when body parsing is disabled.

Fixes GHSA-j3r3-rjcc-vh4q.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: nuwandi-wickramasinghe_zse <nuwandi.wickramasinghe@zalando.de>
Signed-off-by: Mustafa Abdelrahman <mustafa.abdelrahman@zalando.de>
Add a second layer of defence against GHSA-j3r3-rjcc-vh4q: after reading
the request body, validate it with json.Valid before passing it to OPA.
If the bytes are not valid JSON and the body is not truncated, reject with
400 Bad Request.

This closes the bypass where an attacker omits Content-Encoding but sends
a compressed (or otherwise non-JSON) payload declared as application/json.
Without this check, OPA receives unparseable bytes, input.parsed_body is
null, and deny-on-body-field policies fail open.

Truncated bodies are excluded from the check because they carry a partial
JSON fragment by design; OPA already receives x-envoy-auth-partial-body:
true in that case.

Regression tests added to both opaauthorizerequest and opaserveresponse
covering: valid JSON accepted, gzip bytes without Content-Encoding rejected
with 400, arbitrary non-JSON bytes rejected with 400.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: nuwandi-wickramasinghe_zse <nuwandi.wickramasinghe@zalando.de>
Signed-off-by: Mustafa Abdelrahman <mustafa.abdelrahman@zalando.de>
Use %q instead of %s when formatting the untrusted Content-Encoding
header value in the error message, so control characters and other
special bytes from the client are safely escaped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: nuwandi-wickramasinghe_zse <nuwandi.wickramasinghe@zalando.de>
Signed-off-by: Mustafa Abdelrahman <mustafa.abdelrahman@zalando.de>
… helper tests

- Add TestBodyExtractionPartialSemaphoreRelease to verify unused semaphore
  capacity is released immediately when actual body bytes < requested bytes
- Update gzipCompress to accept *testing.T and fail fast on write/close errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: nuwandi-wickramasinghe_zse <nuwandi.wickramasinghe@zalando.de>
Signed-off-by: Mustafa Abdelrahman <mustafa.abdelrahman@zalando.de>
Update gzipBytes to accept *testing.T and call t.Fatalf on write or
close failure instead of silently discarding errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: nuwandi-wickramasinghe_zse <nuwandi.wickramasinghe@zalando.de>
Signed-off-by: Mustafa Abdelrahman <mustafa.abdelrahman@zalando.de>
@MustafaSaber MustafaSaber added the bugfix Bug fixes and patches label Oct 20, 2026
Signed-off-by: Mustafa Abdelrahman <mustafa.abdelrahman@zalando.de>
@MustafaSaber

Copy link
Copy Markdown
Collaborator Author

👍

Signed-off-by: Mustafa Abdelrahman <mustafa.abdelrahman@zalando.de>
@zalando-robot

Copy link
Copy Markdown

Docker image "registry-write.opensource.zalan.do/teapot/skipper:b5585b0f22a0332d703b75b35269ee49c09c1a3b" is not based on an approved base image. Any production deployment relying on this image will be blocked.

To create a compliant Docker image of your application, you must reference an allowed Docker image as its base image in your Dockerfile. This base image must come from the Zalando Container Registry namespace library and use a recommended version as listed in the documentation.

@MustafaSaber

Copy link
Copy Markdown
Collaborator Author

👍

@MustafaSaber
MustafaSaber merged commit 73aba72 into master Oct 20, 2026
23 checks passed
@MustafaSaber
MustafaSaber deleted the opa-gzip-fix branch October 20, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Bug fixes and patches

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants