Skip to content

fix(middleware): detect and handle gzip/zstd automatically and return 415 for invalid encodings#4936

Open
nerimoe wants to merge 1 commit into
QuantumNous:mainfrom
nerimoe:main
Open

fix(middleware): detect and handle gzip/zstd automatically and return 415 for invalid encodings#4936
nerimoe wants to merge 1 commit into
QuantumNous:mainfrom
nerimoe:main

Conversation

@nerimoe
Copy link
Copy Markdown

@nerimoe nerimoe commented May 18, 2026

⚠️ 提交说明 / PR Notice

📝 变更描述 / Description

  • OpenAI Codex 在开启远程控制后会使用 zstd 来压缩body,于是支援了zstd
  • 当客户端发来的请求头没有声明压缩时会判断body的前4个字节是否符合gzip或zstd然后使用对应方式解压
  • 现在在请求头中遇到不支持的编码会拒绝 而不是直接不处理,防止一些意义不明的错误
  • 修改了错误响应

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

[INFO] 2026/05/18 - 15:44:42 | 20260518064426450124000G6bjpudSQlpdDP3d | record consume log: userId=1, params={"channel_id":1,"prompt_tokens":143230,"completion_tokens":51,"model_name":"gpt-5.5","token_name":"default","quota":5382600,"content":"","token_id":1,"use_time_seconds":16,"is_stream":true,"group":"default","other":{"admin_info":{"channel_affinity":{"channel_id":1,"key_fp":"34407c4d","key_hint":"019e...66b1","key_key":"","key_path":"prompt_cache_key","key_source":"gjson","model":"gpt-5.5","override_template":{"applied":true,"param_override_keys":1,"rule_name":"codex cli trace"},"reason":"codex cli trace","request_path":"/v1/responses","rule_name":"codex cli trace","selected_group":"default","using_group":"default"},"use_channel":["1"]},"billing_source":"wallet","cache_ratio":1,"cache_tokens":142720,"completion_ratio":6,"frt":10952,"group_ratio":1,"model_price":-1,"model_ratio":37.5,"reasoning_effort":"high","request_conversion":["OpenAI Responses"],"request_path":"/v1/responses","stream_status":{"end_reason":"eof","status":"ok"},"user_group_ratio":-1}} 

Summary by CodeRabbit

  • Bug Fixes

    • Improved request decompression with auto-detection for gzip and zstd payloads.
    • Enhanced error handling that returns structured JSON for invalid compressed data.
    • Returns HTTP 415 with detected encoding for unsupported content encodings.
    • Ensures decoded request streams enforce configured size limits and properly manage content headers.
  • Chores

    • Promoted a compression library dependency from indirect to direct in module declarations.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Walkthrough

Adds buffered reading and Content-Encoding normalization to DecompressRequestMiddleware, auto-detects gzip/zstd by peeking magic bytes when the header is empty/identity, adjusts decoder paths and close semantics, returns structured JSON errors for invalid encodings, and marks the compress module as a direct go.mod dependency.

Changes

Request Decompression Improvements

Layer / File(s) Summary
Direct compress dependency
go.mod
github.com/klauspost/compress v1.18.0 is marked as a direct requirement instead of indirect.
Buffering and encoding normalization / auto-detection
middleware/gzip.go
Imports for bufio, strings, and zstd decoder added; Content-Encoding is lowercased/trimmed; a buffered reader peeks magic bytes when header is empty/identity to detect gzip or zstd.
Decompression handlers and error responses
middleware/gzip.go
Decoding paths use the buffered reader with max-size enforcement; Content-Length is cleared and Content-Encoding removed after decoding for gzip/br/zstd; invalid gzip/zstd abort with JSON 400; unsupported encodings close body and return 415 JSON including detected encoding.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 I peeked the bytes with a twitch of my nose,
Buffered the stream where the compressed data flows,
Gzip and zstd now dance in the light,
Errors are tidy, returned polite,
Hopping off, the middleware hums as it goes.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: automatic detection and handling of gzip/zstd compression, plus returning 415 for invalid encodings, which aligns with the core modifications in middleware/gzip.go.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
middleware/gzip.go (1)

73-109: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear the compressed Content-Length after swapping in a decompressed body.

These branches replace c.Request.Body with a decompressor, but c.Request.ContentLength and the Content-Length header still describe the compressed payload. Downstream code that trusts those values can under-read the body or forward an invalid length.

💡 Suggested fix
 		origBody := c.Request.Body
 		wrapMaxBytes := func(body io.ReadCloser) io.ReadCloser {
 			return http.MaxBytesReader(c.Writer, body, maxBytes)
 		}
+		clearContentLength := func() {
+			c.Request.ContentLength = -1
+			c.Request.Header.Del("Content-Length")
+		}
@@
 		case "gzip":
 			gzipReader, err := gzip.NewReader(br)
@@
 			c.Request.Body = wrapMaxBytes(&readCloser{
 				Reader: gzipReader,
@@
 			})
+			clearContentLength()
 			c.Request.Header.Del("Content-Encoding")
 		case "br":
@@
 			c.Request.Body = wrapMaxBytes(&readCloser{
 				Reader: reader,
@@
 			})
+			clearContentLength()
 			c.Request.Header.Del("Content-Encoding")
 		case "zstd":
@@
 			c.Request.Body = wrapMaxBytes(&readCloser{
 				Reader: decoder,
@@
 			})
+			clearContentLength()
 			c.Request.Header.Del("Content-Encoding")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@middleware/gzip.go` around lines 73 - 109, After replacing c.Request.Body
with a decompressor (in the gzip, br, and zstd branches using gzipReader,
brotli.NewReader, and zstd.NewReader via wrapMaxBytes/readCloser), clear the
compressed length so downstream consumers don't trust stale values: delete the
"Content-Length" header (c.Request.Header.Del("Content-Length")) and set
c.Request.ContentLength = -1 (unknown) immediately after swapping in the
decompressed body in each branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@middleware/gzip.go`:
- Around line 91-100: zstd.NewReader(br) defers validation until Read, so create
the decoder as you do (decoder := zstd.NewReader(br)) and then perform an
immediate small validation read from decoder (e.g., try reading a single byte or
call a decoder-specific validate method) and if that read returns an error close
origBody and decoder and call c.AbortWithStatusJSON with the same invalid zstd
response; ensure you also close decoder on success/failure to avoid leaks and
keep the same error message/path used in the current block (origBody.Close(),
c.AbortWithStatusJSON(...)).
- Around line 65-70: Replace the direct c.AbortWithStatusJSON(...) calls in
middleware/gzip.go (the blocks emitting JSON for "invalid gzip body" and the
similar responses at the other two locations) with manual response writing that
uses common.Marshal() to serialize the body, sets the "Content-Type" header to
"application/json", writes the appropriate HTTP status code, writes the
marshaled bytes to c.Writer, and then calls c.Abort(); if common.Marshal()
returns an error, write a 500 status (and optionally an empty body) to fail
safely. Locate the response blocks by the error JSON structure (message/type
keys) and update them to use common.Marshal() for marshaling instead of
AbortWithStatusJSON.

---

Outside diff comments:
In `@middleware/gzip.go`:
- Around line 73-109: After replacing c.Request.Body with a decompressor (in the
gzip, br, and zstd branches using gzipReader, brotli.NewReader, and
zstd.NewReader via wrapMaxBytes/readCloser), clear the compressed length so
downstream consumers don't trust stale values: delete the "Content-Length"
header (c.Request.Header.Del("Content-Length")) and set c.Request.ContentLength
= -1 (unknown) immediately after swapping in the decompressed body in each
branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4556e208-bffc-40ec-9227-4f8ebaa31619

📥 Commits

Reviewing files that changed from the base of the PR and between 5dd0d3b and 1f694a6.

📒 Files selected for processing (2)
  • go.mod
  • middleware/gzip.go

Comment thread middleware/gzip.go
Comment thread middleware/gzip.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compressed /v1/responses request body is parsed as JSON

1 participant