Skip to content

fix: use shared HTTP client for sync and model fetch requests - #4467

Open
seefs001 wants to merge 3 commits into
QuantumNous:mainfrom
seefs001:fix/use-shared-http-client
Open

fix: use shared HTTP client for sync and model fetch requests#4467
seefs001 wants to merge 3 commits into
QuantumNous:mainfrom
seefs001:fix/use-shared-http-client

Conversation

@seefs001

@seefs001 seefs001 commented Apr 26, 2026

Copy link
Copy Markdown
Collaborator

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)
部分逻辑没有使用项目通用的http.Client,导致HTTP_PROXY等环境变量配置无效。
涉及:

  • 模型广场同步模型数据
  • 同步上游模型价格
  • OpenAI渠道类型同步上游模型列表
  • Ollama API操作
  • 阿里图像 task polling

他们都是可以信任的请求地址,不能使用默认的redirect fetch protection策略,对默认的http.Client增加option跳过拦截,另对通知场景(Bark / Gotify )的http请求也跳过redirect fetch protection。

🚀 变更类型 / 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

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

Summary by CodeRabbit

  • Refactor
    • Centralized HTTP client usage for more efficient, consistent request handling and resource reuse.
    • Introduced explicit option to allow trusted redirects; proxy-backed clients honor this setting.
    • Per-request timeout behavior preserved while reusing base clients.
    • Standardized JSON decoding across fetch operations for consistent parsing and error handling.
    • Improved reliability of upstream/model/task requests and notification sends.

- route official model sync through the shared HTTP client
- route upstream ratio sync through the shared HTTP client
- route channel model fetch through the shared HTTP client
- route Ollama model operations through the shared HTTP client
- route Ali image task polling through the shared HTTP client
@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Refactors HTTP client acquisition across controllers, relays, and service layer to use centralized service.GetHttpClient (with WithTrustedRedirects option and fallback to http.DefaultClient), replaces local custom transport/client construction, and switches JSON decoding in model sync to common.Unmarshal.

Changes

Cohort / File(s) Summary
Service HTTP client API
service/http_client.go
Adds HTTPClientOption and WithTrustedRedirects(), creates shared default and "trusted redirects" clients, extends GetHttpClient/proxy helpers to accept options and cache by proxy+trust flag; centralizes client/transport construction.
Controller: model/channel/ratio sync
controller/channel.go, controller/model_sync.go, controller/ratio_sync.go
Replaces bespoke per-call/custom http.Client and transport logic with service.GetHttpClient(...WithTrustedRedirects()) (fallback to http.DefaultClient); model_sync switches JSON decoding from encoding/json to common.Unmarshal for primary and fallback paths.
Relay: Ollama & Ali integrations
relay/channel/ollama/relay-ollama.go, relay/channel/ali/image.go
Introduce getOllamaHTTPClient(timeout) and replace direct http.Client{} construction with service.GetHttpClient(...) calls; preserve per-endpoint timeouts by shallow-copying base client when needed.
Controller: billing and task adaptors
controller/channel-billing.go, relay/channel/task/ali/adaptor.go
Proxy-backed and task-status HTTP clients now requested with WithTrustedRedirects() option when building proxied clients; request construction and error handling unchanged.
User notification
service/user_notify.go
Non-worker HTTP sends (Bark/Gotify) now use GetHttpClient(WithTrustedRedirects()) instead of default client.

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Poem

🐇 I hopped through headers, TLS, and timeouts fair,
Shared clients now nest in one cozy lair,
No more new transports sprung each request,
My paws tidy code — the rabbits are impressed,
Hooray for fewer resets, and fewer cares! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: switching to shared HTTP clients for sync and model fetch requests to respect environment proxy settings.
Linked Issues check ✅ Passed Changes comprehensively address issue #4466 by replacing hardcoded HTTP clients with shared clients throughout affected areas to respect container and environment proxy settings.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue: switching from custom HTTP clients to shared clients in model sync, upstream price sync, model fetch, and notification operations.

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
relay/channel/ollama/relay-ollama.go (2)

496-496: Optional: align with JSON wrapper convention while touching this function.

FetchOllamaVersion is being modified for the shared client; line 525 still uses json.Unmarshal directly. Consider switching to common.Unmarshal for consistency (other handlers in this file already use common.Unmarshal/common.Marshal).

♻️ Proposed change
-	if err := json.Unmarshal(body, &versionResp); err != nil {
+	if err := common.Unmarshal(body, &versionResp); err != nil {
 		return "", fmt.Errorf("解析响应失败: %v", err)
 	}

As per coding guidelines: "All JSON marshal/unmarshal operations MUST use wrapper functions from common/json.go ... Do NOT directly import or call encoding/json in business code."

Also applies to: 525-525

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/ollama/relay-ollama.go` at line 496, The FetchOllamaVersion
flow uses the shared HTTP client created by getOllamaHTTPClient but still calls
json.Unmarshal directly; replace that direct encoding/json call with the
project's wrapper common.Unmarshal and ensure any corresponding Marshal usage
follows common.Marshal conventions so FetchOllamaVersion (and its response
handling) aligns with other handlers in this file.

22-36: Helper LGTM, with one optional polish.

The shallow copy works correctly here (http.Client has no lockable fields, and Transport is shared so connection pooling is preserved). A more idiomatic alternative would be applying the per-call timeout via context.WithTimeout on the request and always returning the shared client unchanged — that avoids allocating a new http.Client per call. Not blocking.

// Alternative sketch (callers would pass ctx-bound requests):
// ctx, cancel := context.WithTimeout(parentCtx, timeout)
// req, _ := http.NewRequestWithContext(ctx, ...)
// service.GetHttpClient().Do(req)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/ollama/relay-ollama.go` around lines 22 - 36, The
getOllamaHTTPClient helper currently clones service.GetHttpClient() and sets
Timeout per-call; instead, avoid allocating per-call http.Clients by always
returning the shared client from service.GetHttpClient() (or http.DefaultClient
when nil) and apply per-request timeouts via context.WithTimeout in callers;
update callers that use getOllamaHTTPClient to create a context-bound request
(using context.WithTimeout(parentCtx, timeout)) and call the shared client's
Do/DoContext, leaving getOllamaHTTPClient to simply return the shared client
unchanged and keep connection pooling intact.
controller/channel.go (1)

1078-1078: Optional: align JSON decoding with common.DecodeJson.

While here, consider switching this json.NewDecoder(...).Decode(...) to common.DecodeJson(response.Body, &result) for consistency with the JSON wrapper convention used elsewhere in this PR (e.g., controller/model_sync.go).

♻️ Proposed change
-	if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
+	if err := common.DecodeJson(response.Body, &result); err != nil {

As per coding guidelines: "All JSON marshal/unmarshal operations MUST use wrapper functions in common/json.go: common.Marshal(), common.Unmarshal(), common.UnmarshalJsonStr(), common.DecodeJson(), common.GetJsonType(). Do NOT directly import or call encoding/json in business code."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/channel.go` at line 1078, Replace the direct use of
json.NewDecoder(response.Body).Decode(&result) with the shared wrapper
common.DecodeJson(response.Body, &result) to follow the project's JSON helper
convention; update imports to remove direct encoding/json usage if no longer
needed and ensure error handling remains the same (check the returned error from
common.DecodeJson and propagate/log it as before), referencing the existing
symbols json.NewDecoder, response.Body, result and the common.DecodeJson helper.
controller/model_sync.go (1)

149-157: Nit: dual-decode failure surfaces only the envelope error.

When both the envelope decode and the array fallback fail, lastErr = err loses err2, which is usually the more informative failure for the array path. Consider wrapping both, e.g.:

♻️ Proposed change
-				if err := common.Unmarshal(buf, out); err != nil {
+				if errEnv := common.Unmarshal(buf, out); errEnv != nil {
 					// Try decode as pure array
 					var arr []T
-					if err2 := common.Unmarshal(buf, &arr); err2 != nil {
-						lastErr = err
+					if errArr := common.Unmarshal(buf, &arr); errArr != nil {
+						lastErr = fmt.Errorf("envelope decode: %v; array decode: %w", errEnv, errArr)
 						return
 					}

Same pattern applies to the 304 branch. Purely diagnostic; behavior unchanged.

Also applies to: 174-182

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/model_sync.go` around lines 149 - 157, When common.Unmarshal fails
first (err) and the fallback array decode (err2) also fails, update the error
handling to preserve both failures instead of assigning only lastErr = err;
e.g., set lastErr to a wrapped/combined error that includes both err and err2
(using fmt.Errorf or errors.Join) so diagnostics show which decode failed. Apply
the same change to the similar 304-branch error path so both the envelope and
array decode errors are preserved for debugging (referencing common.Unmarshal,
variables err and err2, and lastErr).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controller/ratio_sync.go`:
- Around line 70-75: getRatioSyncHTTPClient currently returns
service.GetHttpClient() or http.DefaultClient but dropped a previous custom
dialer that tried IPv4 first for github.io hosts, causing regressions for users
who rely on IPv4 reachability; restore a targeted fallback by returning an
*http.Client whose Transport uses Proxy: http.ProxyFromEnvironment and a
DialContext that, for hosts matching "*.github.io" or "basellm.github.io", first
attempts a "tcp4" dial and on failure tries "tcp6" (using net.Dialer.DialContext
for each); leave other hosts to the normal DialContext behavior; implement this
inside getRatioSyncHTTPClient only when service.GetHttpClient() is nil so
existing shared clients continue to be used.

---

Nitpick comments:
In `@controller/channel.go`:
- Line 1078: Replace the direct use of
json.NewDecoder(response.Body).Decode(&result) with the shared wrapper
common.DecodeJson(response.Body, &result) to follow the project's JSON helper
convention; update imports to remove direct encoding/json usage if no longer
needed and ensure error handling remains the same (check the returned error from
common.DecodeJson and propagate/log it as before), referencing the existing
symbols json.NewDecoder, response.Body, result and the common.DecodeJson helper.

In `@controller/model_sync.go`:
- Around line 149-157: When common.Unmarshal fails first (err) and the fallback
array decode (err2) also fails, update the error handling to preserve both
failures instead of assigning only lastErr = err; e.g., set lastErr to a
wrapped/combined error that includes both err and err2 (using fmt.Errorf or
errors.Join) so diagnostics show which decode failed. Apply the same change to
the similar 304-branch error path so both the envelope and array decode errors
are preserved for debugging (referencing common.Unmarshal, variables err and
err2, and lastErr).

In `@relay/channel/ollama/relay-ollama.go`:
- Line 496: The FetchOllamaVersion flow uses the shared HTTP client created by
getOllamaHTTPClient but still calls json.Unmarshal directly; replace that direct
encoding/json call with the project's wrapper common.Unmarshal and ensure any
corresponding Marshal usage follows common.Marshal conventions so
FetchOllamaVersion (and its response handling) aligns with other handlers in
this file.
- Around line 22-36: The getOllamaHTTPClient helper currently clones
service.GetHttpClient() and sets Timeout per-call; instead, avoid allocating
per-call http.Clients by always returning the shared client from
service.GetHttpClient() (or http.DefaultClient when nil) and apply per-request
timeouts via context.WithTimeout in callers; update callers that use
getOllamaHTTPClient to create a context-bound request (using
context.WithTimeout(parentCtx, timeout)) and call the shared client's
Do/DoContext, leaving getOllamaHTTPClient to simply return the shared client
unchanged and keep connection pooling intact.
🪄 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: fc14d686-38c2-450f-b809-df6bcfe283b4

📥 Commits

Reviewing files that changed from the base of the PR and between f2f3410 and 9c4d512.

📒 Files selected for processing (5)
  • controller/channel.go
  • controller/model_sync.go
  • controller/ratio_sync.go
  • relay/channel/ali/image.go
  • relay/channel/ollama/relay-ollama.go

Comment thread controller/ratio_sync.go
Comment on lines +70 to +75
func getRatioSyncHTTPClient() *http.Client {
if client := service.GetHttpClient(); client != nil {
return client
}
return http.DefaultClient
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n controller/ratio_sync.go | head -100

Repository: QuantumNous/new-api

Length of output: 3139


🏁 Script executed:

git log --oneline -20 controller/ratio_sync.go

Repository: QuantumNous/new-api

Length of output: 134


🏁 Script executed:

rg "getRatioSyncHTTPClient|GetHttpClient" -B 3 -A 5

Repository: QuantumNous/new-api

Length of output: 20192


🏁 Script executed:

rg "github\.io" --type go

Repository: QuantumNous/new-api

Length of output: 496


🏁 Script executed:

rg "IPv4|IPv6|Dial" controller/ --type go -B 2 -A 2

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

git show 9c4d5126 --stat

Repository: QuantumNous/new-api

Length of output: 50375


🏁 Script executed:

git show 9c4d5126 -- controller/ratio_sync.go | head -150

Repository: QuantumNous/new-api

Length of output: 4188


🏁 Script executed:

cat -n service/http_client.go | head -80

Repository: QuantumNous/new-api

Length of output: 2874


🏁 Script executed:

cat -n controller/ratio_sync.go | sed -n '125,215p'

Repository: QuantumNous/new-api

Length of output: 3493


🏁 Script executed:

cat -n controller/model_sync.go | sed -n '1,100p'

Repository: QuantumNous/new-api

Length of output: 3318


🏁 Script executed:

rg "IPv4|IPv6|Dial|github\.io" --type go -B 5 -A 5

Repository: QuantumNous/new-api

Length of output: 10583


🏁 Script executed:

git log --all --oneline -- controller/ratio_sync.go

Repository: QuantumNous/new-api

Length of output: 1189


🏁 Script executed:

git log --all --oneline -50 | grep -i ratio

Repository: QuantumNous/new-api

Length of output: 388


🏁 Script executed:

git show 55c82713:controller/ratio_sync.go 2>/dev/null | head -100

Repository: QuantumNous/new-api

Length of output: 2306


🏁 Script executed:

rg "retryIPv4|fallback|ipv4.*ipv6|github\.io.*retry" --type go

Repository: QuantumNous/new-api

Length of output: 2906


🏁 Script executed:

cat -n service/http_client.go | sed -n '80,169p'

Repository: QuantumNous/new-api

Length of output: 3107


🏁 Script executed:

git log --all --oneline -- controller/ratio_sync.go

Repository: QuantumNous/new-api

Length of output: 1189


🏁 Script executed:

git show 55c82713 -- controller/ratio_sync.go | grep -A 20 -B 5 "getRatioSyncHTTPClient\|IPv4\|IPv6\|fallback\|Dial" | head -60

Repository: QuantumNous/new-api

Length of output: 2806


🏁 Script executed:

git log -p --all -S "github.io" -- "*.go" | head -200

Repository: QuantumNous/new-api

Length of output: 45


Shared client adoption LGTM; note: removed github.io IPv4→IPv6 dial fallback.

The helper and swap to client.Do(httpReq) are correct and enable HTTP_PROXY/HTTPS_PROXY env vars (fixing #4466).

The prior implementation at commit 55c8271 included a custom dialer that tried IPv4 first for *.github.io, falling back to IPv6 on failure. This fallback is now gone. For users without a proxy configured but relying on IPv4 reachability for basellm.github.io, this surfaces a connectivity regression. Consider either:

  • Adding a dedicated dialer fallback for *.github.io preset hosts, or
  • Documenting that the recommended fix for github.io reachability issues is now HTTP_PROXY/HTTPS_PROXY.

The retry loop at lines 200-208 safely reuses the same httpReq since the body is nil.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/ratio_sync.go` around lines 70 - 75, getRatioSyncHTTPClient
currently returns service.GetHttpClient() or http.DefaultClient but dropped a
previous custom dialer that tried IPv4 first for github.io hosts, causing
regressions for users who rely on IPv4 reachability; restore a targeted fallback
by returning an *http.Client whose Transport uses Proxy:
http.ProxyFromEnvironment and a DialContext that, for hosts matching
"*.github.io" or "basellm.github.io", first attempts a "tcp4" dial and on
failure tries "tcp6" (using net.Dialer.DialContext for each); leave other hosts
to the normal DialContext behavior; implement this inside getRatioSyncHTTPClient
only when service.GetHttpClient() is nil so existing shared clients continue to
be used.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
service/http_client.go (1)

145-214: Optional: hoist the duplicated finalize block.

The HTTP/HTTPS and SOCKS5 branches both repeat the if opts.trustedRedirects { client.CheckRedirect = nil } + client.Timeout = ... + cache-store sequence. A small helper finalizeProxyClient(client, cacheKey, opts) would reduce duplication and keep future SSRF/timeout changes in one place. Not blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/http_client.go` around lines 145 - 214, There is duplicated
finalization logic after creating the http.Client in both the "http/https" and
"socks5/socks5h" branches (the if opts.trustedRedirects { client.CheckRedirect =
nil } + client.Timeout = ... + proxyClients cache-store block); extract that
into a helper function (e.g., finalizeProxyClient(client *http.Client, cacheKey
string, opts proxyOptions)) that sets CheckRedirect when opts.trustedRedirects
is true, sets client.Timeout = time.Duration(common.RelayTimeout)*time.Second,
acquires proxyClientLock, stores proxyClients[cacheKey] = client, releases the
lock, and returns the client; then replace the duplicated sequences in both
branches with a call to finalizeProxyClient so all timeout/SSRF-related
finalization is centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@service/user_notify.go`:
- Line 172: The change replacing GetHttpClient() with
GetHttpClient(WithTrustedRedirects()) for Bark/Gotify removes the per-redirect
SSRF protection (CheckRedirect becomes nil) so only the initial URL is validated
by ValidateURLWithFetchSetting and redirects to internal IPs can be followed
(dangerous for Gotify POSTs). Revert these callers to use the protected client
(remove WithTrustedRedirects) or explicitly restore a CheckRedirect that
enforces ValidateURLWithFetchSetting on each redirect; update the Bark and
Gotify call sites that call GetHttpClient/WithTrustedRedirects (references:
GetHttpClient, WithTrustedRedirects, ValidateURLWithFetchSetting, Bark/Gotify
send handlers) so redirects are not allowed to reach private/internal addresses
unless explicitly trusted.

---

Nitpick comments:
In `@service/http_client.go`:
- Around line 145-214: There is duplicated finalization logic after creating the
http.Client in both the "http/https" and "socks5/socks5h" branches (the if
opts.trustedRedirects { client.CheckRedirect = nil } + client.Timeout = ... +
proxyClients cache-store block); extract that into a helper function (e.g.,
finalizeProxyClient(client *http.Client, cacheKey string, opts proxyOptions))
that sets CheckRedirect when opts.trustedRedirects is true, sets client.Timeout
= time.Duration(common.RelayTimeout)*time.Second, acquires proxyClientLock,
stores proxyClients[cacheKey] = client, releases the lock, and returns the
client; then replace the duplicated sequences in both branches with a call to
finalizeProxyClient so all timeout/SSRF-related finalization is centralized.
🪄 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: 63b211ff-9144-4eb5-8da4-0c75b2c1a3ed

📥 Commits

Reviewing files that changed from the base of the PR and between 9c4d512 and 6f63835.

📒 Files selected for processing (8)
  • controller/channel-billing.go
  • controller/channel.go
  • controller/model_sync.go
  • controller/ratio_sync.go
  • relay/channel/ollama/relay-ollama.go
  • relay/channel/task/ali/adaptor.go
  • service/http_client.go
  • service/user_notify.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • controller/ratio_sync.go

Comment thread service/user_notify.go

// 发送请求
client := GetHttpClient()
client := GetHttpClient(WithTrustedRedirects())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Question: should Bark/Gotify really opt into WithTrustedRedirects?

Switching these from GetHttpClient() to GetHttpClient(WithTrustedRedirects()) disables the per-redirect SSRF check (CheckRedirect becomes nil). The pre-flight ValidateURLWithFetchSetting (lines 158/252) only validates the initial URL — redirects to private/internal IPs are now followed. For Gotify in particular this is a POST with a JSON payload, so a malicious or compromised endpoint that returns a 30x toward http://127.0.0.1:<internal-port> can trigger an internal request originating from the server.

Bark/Gotify URLs are user-controlled (per-user setting), so the blast radius is limited to self-targeting, but this is still a regression vs. the previous behavior and is outside the stated PR scope (model sync / proxy fix). If there’s no concrete reason these flows must follow untrusted redirects, consider keeping the protected client here.

Suggested revert
-		client := GetHttpClient(WithTrustedRedirects())
+		client := GetHttpClient()
 		resp, err = client.Do(req)

(applies to both Bark at line 172 and Gotify at line 267)

Also applies to: 267-267

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/user_notify.go` at line 172, The change replacing GetHttpClient()
with GetHttpClient(WithTrustedRedirects()) for Bark/Gotify removes the
per-redirect SSRF protection (CheckRedirect becomes nil) so only the initial URL
is validated by ValidateURLWithFetchSetting and redirects to internal IPs can be
followed (dangerous for Gotify POSTs). Revert these callers to use the protected
client (remove WithTrustedRedirects) or explicitly restore a CheckRedirect that
enforces ValidateURLWithFetchSetting on each redirect; update the Bark and
Gotify call sites that call GetHttpClient/WithTrustedRedirects (references:
GetHttpClient, WithTrustedRedirects, ValidateURLWithFetchSetting, Bark/Gotify
send handlers) so redirects are not allowed to reach private/internal addresses
unless explicitly trusted.

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.

错误:获取上游模型失败: connection reset by peer

1 participant