Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
# 流模式无响应超时时间,单位秒,如果出现空补全可以尝试改为更大值
# STREAMING_TIMEOUT=300

# TLS / HTTP 跳过验证设置
# TLS_INSECURE_SKIP_VERIFY=false

# Gemini 识别图片 最大图片数量
# GEMINI_VISION_MAX_IMAGE_NUM=16

Expand Down
4 changes: 4 additions & 0 deletions common/constants.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package common

import (
"crypto/tls"
//"os"
//"strconv"
"sync"
Expand Down Expand Up @@ -73,6 +74,9 @@ var MemoryCacheEnabled bool

var LogConsumeEnabled = true

var TLSInsecureSkipVerify bool
var InsecureTLSConfig = &tls.Config{InsecureSkipVerify: true}

var SMTPServer = ""
var SMTPPort = 587
var SMTPSSLEnabled = false
Expand Down
11 changes: 11 additions & 0 deletions common/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"flag"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -81,6 +82,16 @@ func InitEnv() {
DebugEnabled = os.Getenv("DEBUG") == "true"
MemoryCacheEnabled = os.Getenv("MEMORY_CACHE_ENABLED") == "true"
IsMasterNode = os.Getenv("NODE_TYPE") != "slave"
TLSInsecureSkipVerify = GetEnvOrDefaultBool("TLS_INSECURE_SKIP_VERIFY", false)
if TLSInsecureSkipVerify {
if tr, ok := http.DefaultTransport.(*http.Transport); ok && tr != nil {
if tr.TLSClientConfig != nil {
tr.TLSClientConfig.InsecureSkipVerify = true
} else {
tr.TLSClientConfig = InsecureTLSConfig
}
}
}

// Parse requestInterval and set RequestInterval
requestInterval, _ = strconv.Atoi(os.Getenv("POLLING_INTERVAL"))
Expand Down
17 changes: 15 additions & 2 deletions controller/model_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ func newHTTPClient() *http.Client {
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: time.Duration(timeoutSec) * time.Second,
}
if common.TLSInsecureSkipVerify {
transport.TLSClientConfig = common.InsecureTLSConfig
}
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
Expand All @@ -115,7 +118,17 @@ func newHTTPClient() *http.Client {
return &http.Client{Transport: transport}
}

var httpClient = newHTTPClient()
var (
httpClientOnce sync.Once
httpClient *http.Client
)

func getHTTPClient() *http.Client {
httpClientOnce.Do(func() {
httpClient = newHTTPClient()
})
return httpClient
}

func fetchJSON[T any](ctx context.Context, url string, out *upstreamEnvelope[T]) error {
var lastErr error
Expand All @@ -138,7 +151,7 @@ func fetchJSON[T any](ctx context.Context, url string, out *upstreamEnvelope[T])
}
cacheMutex.RUnlock()

resp, err := httpClient.Do(req)
resp, err := getHTTPClient().Do(req)
if err != nil {
lastErr = err
// backoff with jitter
Expand Down
4 changes: 4 additions & 0 deletions controller/ratio_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sync"
"time"

"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/logger"

"github.com/QuantumNous/new-api/dto"
Expand Down Expand Up @@ -110,6 +111,9 @@ func FetchUpstreamRatios(c *gin.Context) {

dialer := &net.Dialer{Timeout: 10 * time.Second}
transport := &http.Transport{MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, ResponseHeaderTimeout: 10 * time.Second}
if common.TLSInsecureSkipVerify {
transport.TLSClientConfig = common.InsecureTLSConfig
}
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
Expand Down
39 changes: 24 additions & 15 deletions service/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ func InitHttpClient() {
ForceAttemptHTTP2: true,
Proxy: http.ProxyFromEnvironment, // Support HTTP_PROXY, HTTPS_PROXY, NO_PROXY env vars
}
if common.TLSInsecureSkipVerify {
transport.TLSClientConfig = common.InsecureTLSConfig
}
Comment on lines +43 to +45

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:

#!/bin/bash
# Search for TLS insecure skip verify references and any associated logging
rg -n -C3 'TLSInsecureSkipVerify|TLS_INSECURE_SKIP_VERIFY' --type=go

Repository: QuantumNous/new-api

Length of output: 3240


🏁 Script executed:

#!/bin/bash
# View the full context of TLSInsecureSkipVerify initialization in common/init.go
rg -n -B5 -A15 'if TLSInsecureSkipVerify' common/init.go

Repository: QuantumNous/new-api

Length of output: 984


🏁 Script executed:

#!/bin/bash
# Check for any logging related to TLS or insecure in common/init.go
# Also check imports to see what logging is available
head -50 common/init.go

Repository: QuantumNous/new-api

Length of output: 1245


🏁 Script executed:

#!/bin/bash
# Search for any logging statements in common/init.go
rg -n 'log\.|fmt\.Print|slog\.' common/init.go

Repository: QuantumNous/new-api

Length of output: 825


Add a warning log when TLS insecure skip verification is enabled.

The code correctly applies insecure TLS configuration across all transport instances. However, there is currently no warning logged in common/init.go when TLSInsecureSkipVerify is enabled via the environment variable. Since TLS verification bypass is a significant security setting, operators should be explicitly notified. Add a warning log similar to the SESSION_SECRET warning pattern already in the codebase (lines 52-54 of common/init.go).

🤖 Prompt for AI Agents
In `@service/http_client.go` around lines 43 - 45, Add a warning log when the TLS
verification bypass is enabled: where TLSInsecureSkipVerify is evaluated during
initialization (same place/session-secret check in common/init.go that emits the
SESSION_SECRET warning), detect if common.TLSInsecureSkipVerify is true and emit
a warning via the same logger used for SESSION_SECRET (e.g., processLogger.Warn
/ Warnf) stating that TLS insecure skip verify is enabled and is a security
risk; this mirrors the existing SESSION_SECRET warning pattern so operators are
explicitly notified.


if common.RelayTimeout == 0 {
httpClient = &http.Client{
Expand Down Expand Up @@ -102,13 +105,17 @@ func NewProxyHttpClient(proxyURL string) (*http.Client, error) {

switch parsedURL.Scheme {
case "http", "https":
transport := &http.Transport{
MaxIdleConns: common.RelayMaxIdleConns,
MaxIdleConnsPerHost: common.RelayMaxIdleConnsPerHost,
ForceAttemptHTTP2: true,
Proxy: http.ProxyURL(parsedURL),
}
if common.TLSInsecureSkipVerify {
transport.TLSClientConfig = common.InsecureTLSConfig
}
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: common.RelayMaxIdleConns,
MaxIdleConnsPerHost: common.RelayMaxIdleConnsPerHost,
ForceAttemptHTTP2: true,
Proxy: http.ProxyURL(parsedURL),
},
Transport: transport,
CheckRedirect: checkRedirect,
}
client.Timeout = time.Duration(common.RelayTimeout) * time.Second
Expand Down Expand Up @@ -137,17 +144,19 @@ func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
return nil, err
}

client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: common.RelayMaxIdleConns,
MaxIdleConnsPerHost: common.RelayMaxIdleConnsPerHost,
ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
},
transport := &http.Transport{
MaxIdleConns: common.RelayMaxIdleConns,
MaxIdleConnsPerHost: common.RelayMaxIdleConnsPerHost,
ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
},
CheckRedirect: checkRedirect,
}
if common.TLSInsecureSkipVerify {
transport.TLSClientConfig = common.InsecureTLSConfig
}

client := &http.Client{Transport: transport, CheckRedirect: checkRedirect}
Comment on lines +147 to +159

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

🌐 Web query:

golang.org/x/net/proxy ContextDialer interface

💡 Result:

The ContextDialer interface (package golang.org/x/net/proxy) is:

type ContextDialer interface {
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}

Purpose: a Dialer that supports context-aware dialing (cancellation/deadlines). It complements the older Dialer interface (Dial(network, addr string) (net.Conn, error)); custom Dialers should implement ContextDialer when possible. The package’s Dial function will use DialContext when the configured Dialer implements ContextDialer. Examples of implementers include net.Dialer and the package’s Direct dialer. [1][2]

Sources:

  • pkg.go.dev documentation for golang.org/x/net/proxy. [1]
  • proxy package source (dial.go / direct.go / socks5.go). [2]

🏁 Script executed:

# Find the file to understand context around the code
fd -n "http_client.go" -t f

# Search for imports related to proxy in the file
rg "import|proxy" service/http_client.go -A 2 -B 2

Repository: QuantumNous/new-api

Length of output: 182


🌐 Web query:

golang.org/x/net/proxy SOCKS5 ContextDialer implementation

💡 Result:

Short answer: golang.org/x/net/proxy.SOCKS5 creates and returns an internal socks.Dialer (from golang.org/x/net/internal/socks). If you pass a non-nil forward Dialer and it implements proxy.ContextDialer, SOCKS5 sets the internal Dialer.ProxyDial to call forward.DialContext(ctx,...). If the forward Dialer does not implement ContextDialer, SOCKS5 sets ProxyDial to a wrapper that calls dialContext(ctx, forward, ...). The code is in proxy/socks5.go. [1][2]

Sources:

  • proxy/socks5.go source (golang.org/x/net): lines showing ProxyDial assignment and ContextDialer check. [1]
  • pkg.go.dev documentation for golang.org/x/net/proxy (SOCKS5, ContextDialer, Dialer types). [2]

🌐 Web query:

golang.org/x/net/proxy.SOCKS5 source code DialContext

💡 Result:

The SOCKS5 function (golang.org/x/net/proxy.SOCKS5) sets the internal socks.Dialer.ProxyDial to call the forward Dialer’s DialContext if the provided forward implements proxy.ContextDialer; otherwise it wraps the non-context Dialer with the package helper dialContext. See the source:

  • SOCKS5 selects DialContext from the forward Dialer when available (calls f.DialContext). [1]
  • If forward does not implement ContextDialer, SOCKS5 uses dialContext(ctx, forward, ...). [1][2]
  • The internal socks.Dialer used by SOCKS5 exposes DialContext. [3]

Sources:
[1] golang.org/x/net/proxy/socks5.go (SOCKS5 implementation).
[2] golang.org/x/net/proxy/dial.go (dialContext / ContextDialer definitions).
[3] golang.org/x/net/internal/socks (Dialer has DialContext).


Use the dialer's ContextDialer interface if available to properly handle context cancellation and deadlines.

The DialContext function currently ignores the context parameter and only calls dialer.Dial(network, addr), bypassing any context support. When using golang.org/x/net/proxy.SOCKS5, the returned dialer implements the proxy.ContextDialer interface and exposes a DialContext method that properly respects context cancellation and timeouts. Check if the dialer implements ContextDialer and use that directly instead of wrapping it with a context-ignoring function.

🔧 Proposed fix
 		transport := &http.Transport{
 			MaxIdleConns:        common.RelayMaxIdleConns,
 			MaxIdleConnsPerHost: common.RelayMaxIdleConnsPerHost,
 			ForceAttemptHTTP2:   true,
-			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
-				return dialer.Dial(network, addr)
-			},
+		}
+		if contextDialer, ok := dialer.(proxy.ContextDialer); ok {
+			transport.DialContext = contextDialer.DialContext
+		} else {
+			transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
+				return dialer.Dial(network, addr)
+			}
 		}
 		if common.TLSInsecureSkipVerify {
 			transport.TLSClientConfig = common.InsecureTLSConfig
 		}
🤖 Prompt for AI Agents
In `@service/http_client.go` around lines 147 - 159, The transport's DialContext
wrapper ignores the provided context; update the code that builds the
http.Transport (variable transport) to detect if the dialer implements
proxy.ContextDialer and, if so, assign transport.DialContext to that dialer’s
DialContext method, otherwise fall back to the existing func(ctx, network, addr)
calling dialer.Dial(network, addr). Ensure you still set
transport.TLSClientConfig when common.TLSInsecureSkipVerify and preserve
CheckRedirect on the created http.Client.

client.Timeout = time.Duration(common.RelayTimeout) * time.Second
proxyClientLock.Lock()
proxyClients[proxyURL] = client
Expand Down