fix(security): 脱敏传输层错误中泄露的上游密钥#429
Conversation
Gemini 通道将上游 key 放入 URL query,传输层错误(超时/连接失败) 时 Go 的 url.Error 会把完整 URL 带入错误信息,未脱敏地返回给客户端 并写入请求日志(GHSA-v6w2-mrg5-vc9r) 在 errorMessage/parsedError 计算完成后统一调用 RedactSecret 替换掉 apiKey.KeyValue 子串,对所有渠道生效
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d83f16239
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if secret == "" { | ||
| return text | ||
| } | ||
| return strings.ReplaceAll(text, secret, MaskAPIKey(secret)) |
There was a problem hiding this comment.
Redact URL-encoded key values as well
For Gemini requests, ModifyRequest puts the upstream key into the query string via q.Set(...) and q.Encode(), so a key containing escaped characters such as +, /, = or spaces appears in *url.Error as its percent-encoded form. This replacement only searches for the raw apiKey.KeyValue, so transport errors for those keys still return and persist the encoded credential in full; redact the query-escaped form too before returning/logging the error.
Useful? React with 👍 / 👎.
| if secret == "" { | ||
| return text | ||
| } | ||
| return strings.ReplaceAll(text, secret, MaskAPIKey(secret)) |
There was a problem hiding this comment.
Use a redaction token for short secrets
When the upstream key is 8 bytes or shorter, MaskAPIKey returns the original key, so this ReplaceAll replaces the secret with itself and leaves the transport error unchanged. Since key import only rejects empty strings, a group can have a short upstream key and still leak it to the client/log on the exact error path this helper is meant to protect; use a replacement that always differs from the secret.
Useful? React with 👍 / 👎.
Summary
修复 GHSA-v6w2-mrg5-vc9r:Gemini 通道把上游 API key 放在 URL query(
?key=xxx),当上游请求发生传输层错误(超时/连接失败/DNS)时,Go 的*url.Error字符串包含完整 URL(含明文 key),此前被原样返回给客户端并写入请求日志,任何持有该分组 proxy key 的普通消费者都能拿到运营者的上游 provider key。utils.RedactSecret(text, secret):把 text 中出现的 secret 子串替换为已有的MaskAPIKey脱敏形式internal/proxy/server.go的errorMessage/parsedError计算完成、被用于返回客户端/写日志/更新 key 状态之前,统一调用RedactSecret剔除apiKey.KeyValueTest plan
internal/utils/string_utils_test.go,覆盖多次出现替换、空 secret、secret 不存在三种场景,RED→GREEN 验证go build ./.../go vet ./.../go test ./...全部通过connection refused),确认:UPSTREAM_ERROR消息中 key 已脱敏为sk-t****7890request_logs.error_message的内容同样已脱敏(注意日志记录里另有一个独立的key_value字段,那是管理员本就可见的"本次请求使用了哪个 key"审计字段,与本漏洞无关,不在修复范围内)