From a11b756e9c07cbedc761d93ef3286d275cfa53bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 19:25:11 +0000 Subject: [PATCH 01/28] Initial plan From 06fd58d8f8a3ad50cb28f038eeacf4d8a3579e96 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 19:33:57 +0000 Subject: [PATCH 02/28] feat: set default User-Agent to Chrome with dynamic version based on date Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- app/dns/nameserver_doh.go | 2 ++ app/observatory/burst/ping.go | 3 +++ common/protocol/http/headers.go | 10 ++++++++++ common/protocol/http/headers_test.go | 13 +++++++++++++ infra/conf/transport_authenticators.go | 8 +++----- proxy/http/server.go | 2 +- transport/internet/reality/reality.go | 3 ++- transport/internet/tls/ech.go | 2 ++ 8 files changed, 36 insertions(+), 7 deletions(-) diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go index c0c1af265058..9d6f30f6e725 100644 --- a/app/dns/nameserver_doh.go +++ b/app/dns/nameserver_doh.go @@ -18,6 +18,7 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/protocol/dns" + http_proto "github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/utils" dns_feature "github.com/xtls/xray-core/features/dns" @@ -214,6 +215,7 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, req.Header.Add("Accept", "application/dns-message") req.Header.Add("Content-Type", "application/dns-message") + req.Header.Set("User-Agent", http_proto.ChromeUA()) req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) hc := s.httpClient diff --git a/app/observatory/burst/ping.go b/app/observatory/burst/ping.go index fcb40f1a706a..ea880cdcd88a 100644 --- a/app/observatory/burst/ping.go +++ b/app/observatory/burst/ping.go @@ -6,6 +6,8 @@ import ( "net/http" "time" + http_proto "github.com/xtls/xray-core/common/protocol/http" + "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet/tagged" @@ -61,6 +63,7 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) { if err != nil { return rttFailed, err } + req.Header.Set("User-Agent", http_proto.ChromeUA()) start := time.Now() resp, err := s.httpClient.Do(req) diff --git a/common/protocol/http/headers.go b/common/protocol/http/headers.go index db3aa6705eb2..502fbae7c961 100644 --- a/common/protocol/http/headers.go +++ b/common/protocol/http/headers.go @@ -4,10 +4,20 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/xtls/xray-core/common/net" ) +// ChromeUA generates a Chrome browser User-Agent string. +// The version number changes monthly, starting from 143 in January 2026. +func ChromeUA() string { + t := time.Now() + majorVersion := 143 + (t.Year()-2026)*12 + int(t.Month()) - 1 + return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + + strconv.Itoa(majorVersion) + ".0.0.0 Safari/537.36" +} + // ParseXForwardedFor parses X-Forwarded-For header in http headers, and return the IP list in it. func ParseXForwardedFor(header http.Header) []net.Address { xff := header.Get("X-Forwarded-For") diff --git a/common/protocol/http/headers_test.go b/common/protocol/http/headers_test.go index 80e243aebc55..87870e4b931f 100644 --- a/common/protocol/http/headers_test.go +++ b/common/protocol/http/headers_test.go @@ -115,3 +115,16 @@ func TestParseHost(t *testing.T) { } } } + +func TestChromeUA(t *testing.T) { +ua := ChromeUA() +if !strings.Contains(ua, "Chrome/") { +t.Error("ChromeUA should contain Chrome/ identifier") +} +if !strings.Contains(ua, "Mozilla/5.0") { +t.Error("ChromeUA should contain Mozilla/5.0 prefix") +} +if !strings.HasSuffix(ua, "Safari/537.36") { +t.Error("ChromeUA should end with Safari/537.36") +} +} diff --git a/infra/conf/transport_authenticators.go b/infra/conf/transport_authenticators.go index 2274c5addc01..9b896a9bf437 100644 --- a/infra/conf/transport_authenticators.go +++ b/infra/conf/transport_authenticators.go @@ -4,6 +4,7 @@ import ( "sort" "github.com/xtls/xray-core/common/errors" + http_proto "github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/transport/internet/headers/http" "github.com/xtls/xray-core/transport/internet/headers/noop" "google.golang.org/protobuf/proto" @@ -40,11 +41,8 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) { Value: []string{"www.baidu.com", "www.bing.com"}, }, { - Name: "User-Agent", - Value: []string{ - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", - "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46", - }, + Name: "User-Agent", + Value: []string{http_proto.ChromeUA()}, }, { Name: "Accept-Encoding", diff --git a/proxy/http/server.go b/proxy/http/server.go index 90a07b38a585..a9751e060a05 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -229,7 +229,7 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri // Prevent UA from being set to golang's default ones if request.Header.Get("User-Agent") == "" { - request.Header.Set("User-Agent", "") + request.Header.Set("User-Agent", http_proto.ChromeUA()) } content := &session.Content{ diff --git a/transport/internet/reality/reality.go b/transport/internet/reality/reality.go index 21b185ea1d15..09b4b60db9b2 100644 --- a/transport/internet/reality/reality.go +++ b/transport/internet/reality/reality.go @@ -27,6 +27,7 @@ import ( "github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" + http_proto "github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/transport/internet/tls" "golang.org/x/crypto/hkdf" @@ -222,7 +223,7 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati if req == nil { return } - req.Header.Set("User-Agent", fingerprint.Client) // TODO: User-Agent map + req.Header.Set("User-Agent", http_proto.ChromeUA()) if first && config.Show { fmt.Printf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent()) } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 389d0699adfd..25c2295194a7 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -27,6 +27,7 @@ import ( "github.com/xtls/reality/hpke" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" + http_proto "github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet" "golang.org/x/crypto/cryptobyte" @@ -257,6 +258,7 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b } req.Header.Set("Accept", "application/dns-message") req.Header.Set("Content-Type", "application/dns-message") + req.Header.Set("User-Agent", http_proto.ChromeUA()) req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) resp, err := client.Do(req) From b61d73738e28efcc07657e36aa55dad3f37df06b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 19:34:58 +0000 Subject: [PATCH 03/28] fix: add minimum version constraint for ChromeUA and improve test Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/protocol/http/headers.go | 4 ++++ common/protocol/http/headers_test.go | 24 ++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/common/protocol/http/headers.go b/common/protocol/http/headers.go index 502fbae7c961..02445de62702 100644 --- a/common/protocol/http/headers.go +++ b/common/protocol/http/headers.go @@ -14,6 +14,10 @@ import ( func ChromeUA() string { t := time.Now() majorVersion := 143 + (t.Year()-2026)*12 + int(t.Month()) - 1 + // Ensure minimum version of 143 for dates before January 2026 + if majorVersion < 143 { + majorVersion = 143 + } return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(majorVersion) + ".0.0.0 Safari/537.36" } diff --git a/common/protocol/http/headers_test.go b/common/protocol/http/headers_test.go index 87870e4b931f..0184a623feec 100644 --- a/common/protocol/http/headers_test.go +++ b/common/protocol/http/headers_test.go @@ -117,14 +117,18 @@ func TestParseHost(t *testing.T) { } func TestChromeUA(t *testing.T) { -ua := ChromeUA() -if !strings.Contains(ua, "Chrome/") { -t.Error("ChromeUA should contain Chrome/ identifier") -} -if !strings.Contains(ua, "Mozilla/5.0") { -t.Error("ChromeUA should contain Mozilla/5.0 prefix") -} -if !strings.HasSuffix(ua, "Safari/537.36") { -t.Error("ChromeUA should end with Safari/537.36") -} + ua := ChromeUA() + if !strings.Contains(ua, "Chrome/") { + t.Error("ChromeUA should contain Chrome/ identifier") + } + if !strings.Contains(ua, "Mozilla/5.0") { + t.Error("ChromeUA should contain Mozilla/5.0 prefix") + } + if !strings.HasSuffix(ua, "Safari/537.36") { + t.Error("ChromeUA should end with Safari/537.36") + } + // Verify version is at least the minimum (143) + if !strings.Contains(ua, "Chrome/1") { + t.Error("ChromeUA should have a Chrome version starting with 1") + } } From a7c0037c2b94ac6cd8cfb9114cad29c2f0025d98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:01:42 +0000 Subject: [PATCH 04/28] refactor: move ChromeUA to utils as var, add UA to XHTTP/WS/HTTPUpgrade/gRPC, revert proxy/http Co-authored-by: Fangliding <63339210+Fangliding@users.noreply.github.com> --- app/dns/nameserver_doh.go | 3 +-- app/observatory/burst/ping.go | 4 ++-- common/protocol/http/headers.go | 14 -------------- common/protocol/http/headers_test.go | 17 ----------------- common/utils/browser.go | 18 ++++++++++++++++++ infra/conf/transport_authenticators.go | 4 ++-- proxy/http/server.go | 2 +- transport/internet/grpc/dial.go | 9 +++++++-- transport/internet/httpupgrade/dialer.go | 4 ++++ transport/internet/reality/reality.go | 4 ++-- transport/internet/splithttp/config.go | 6 +++++- transport/internet/tls/ech.go | 3 +-- transport/internet/websocket/config.go | 6 +++++- 13 files changed, 48 insertions(+), 46 deletions(-) create mode 100644 common/utils/browser.go diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go index 9d6f30f6e725..2126bcd85457 100644 --- a/app/dns/nameserver_doh.go +++ b/app/dns/nameserver_doh.go @@ -18,7 +18,6 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/protocol/dns" - http_proto "github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/utils" dns_feature "github.com/xtls/xray-core/features/dns" @@ -215,7 +214,7 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, req.Header.Add("Accept", "application/dns-message") req.Header.Add("Content-Type", "application/dns-message") - req.Header.Set("User-Agent", http_proto.ChromeUA()) + req.Header.Set("User-Agent", utils.ChromeUA) req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) hc := s.httpClient diff --git a/app/observatory/burst/ping.go b/app/observatory/burst/ping.go index ea880cdcd88a..f469643841bc 100644 --- a/app/observatory/burst/ping.go +++ b/app/observatory/burst/ping.go @@ -6,7 +6,7 @@ import ( "net/http" "time" - http_proto "github.com/xtls/xray-core/common/protocol/http" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/features/routing" @@ -63,7 +63,7 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) { if err != nil { return rttFailed, err } - req.Header.Set("User-Agent", http_proto.ChromeUA()) + req.Header.Set("User-Agent", utils.ChromeUA) start := time.Now() resp, err := s.httpClient.Do(req) diff --git a/common/protocol/http/headers.go b/common/protocol/http/headers.go index 02445de62702..db3aa6705eb2 100644 --- a/common/protocol/http/headers.go +++ b/common/protocol/http/headers.go @@ -4,24 +4,10 @@ import ( "net/http" "strconv" "strings" - "time" "github.com/xtls/xray-core/common/net" ) -// ChromeUA generates a Chrome browser User-Agent string. -// The version number changes monthly, starting from 143 in January 2026. -func ChromeUA() string { - t := time.Now() - majorVersion := 143 + (t.Year()-2026)*12 + int(t.Month()) - 1 - // Ensure minimum version of 143 for dates before January 2026 - if majorVersion < 143 { - majorVersion = 143 - } - return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + - strconv.Itoa(majorVersion) + ".0.0.0 Safari/537.36" -} - // ParseXForwardedFor parses X-Forwarded-For header in http headers, and return the IP list in it. func ParseXForwardedFor(header http.Header) []net.Address { xff := header.Get("X-Forwarded-For") diff --git a/common/protocol/http/headers_test.go b/common/protocol/http/headers_test.go index 0184a623feec..80e243aebc55 100644 --- a/common/protocol/http/headers_test.go +++ b/common/protocol/http/headers_test.go @@ -115,20 +115,3 @@ func TestParseHost(t *testing.T) { } } } - -func TestChromeUA(t *testing.T) { - ua := ChromeUA() - if !strings.Contains(ua, "Chrome/") { - t.Error("ChromeUA should contain Chrome/ identifier") - } - if !strings.Contains(ua, "Mozilla/5.0") { - t.Error("ChromeUA should contain Mozilla/5.0 prefix") - } - if !strings.HasSuffix(ua, "Safari/537.36") { - t.Error("ChromeUA should end with Safari/537.36") - } - // Verify version is at least the minimum (143) - if !strings.Contains(ua, "Chrome/1") { - t.Error("ChromeUA should have a Chrome version starting with 1") - } -} diff --git a/common/utils/browser.go b/common/utils/browser.go new file mode 100644 index 000000000000..7f8c416592e4 --- /dev/null +++ b/common/utils/browser.go @@ -0,0 +1,18 @@ +package utils + +import ( +"strconv" +"time" +) + +func browserVersion() int { +now := time.Now() +ver := 143 + (now.Year()-2026)*12 + int(now.Month()) - 1 +if ver < 143 { +ver = 143 +} +return ver +} + +// ChromeUA provides default browser User-Agent. Version 143 = Jan 2026, +1 per month. +var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(browserVersion()) + ".0.0.0 Safari/537.36" diff --git a/infra/conf/transport_authenticators.go b/infra/conf/transport_authenticators.go index 9b896a9bf437..ea286f41d4a1 100644 --- a/infra/conf/transport_authenticators.go +++ b/infra/conf/transport_authenticators.go @@ -4,7 +4,7 @@ import ( "sort" "github.com/xtls/xray-core/common/errors" - http_proto "github.com/xtls/xray-core/common/protocol/http" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet/headers/http" "github.com/xtls/xray-core/transport/internet/headers/noop" "google.golang.org/protobuf/proto" @@ -42,7 +42,7 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) { }, { Name: "User-Agent", - Value: []string{http_proto.ChromeUA()}, + Value: []string{utils.ChromeUA}, }, { Name: "Accept-Encoding", diff --git a/proxy/http/server.go b/proxy/http/server.go index a9751e060a05..90a07b38a585 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -229,7 +229,7 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri // Prevent UA from being set to golang's default ones if request.Header.Get("User-Agent") == "" { - request.Header.Set("User-Agent", http_proto.ChromeUA()) + request.Header.Set("User-Agent", "") } content := &session.Content{ diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 454b32808852..bb426351bee0 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -6,6 +6,7 @@ import ( "time" "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/utils" c "github.com/xtls/xray-core/common/ctx" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" @@ -167,8 +168,12 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in dialOptions = append(dialOptions, grpc.WithInitialWindowSize(grpcSettings.InitialWindowsSize)) } - if grpcSettings.UserAgent != "" { - dialOptions = append(dialOptions, grpc.WithUserAgent(grpcSettings.UserAgent)) + userAgent := grpcSettings.UserAgent + if userAgent == "" { + userAgent = utils.ChromeUA + } + if userAgent != "" { + dialOptions = append(dialOptions, grpc.WithUserAgent(userAgent)) } var grpcDestHost string diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index c10bd97e5d3c..1a5c758d32a3 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/transport/internet" @@ -86,6 +87,9 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * for key, value := range transportConfiguration.Header { AddHeader(req.Header, key, value) } +if req.Header.Get("User-Agent") == "" { + req.Header.Set("User-Agent", utils.ChromeUA) + } req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "websocket") diff --git a/transport/internet/reality/reality.go b/transport/internet/reality/reality.go index 09b4b60db9b2..1f6de2b5b17b 100644 --- a/transport/internet/reality/reality.go +++ b/transport/internet/reality/reality.go @@ -27,7 +27,7 @@ import ( "github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" - http_proto "github.com/xtls/xray-core/common/protocol/http" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/transport/internet/tls" "golang.org/x/crypto/hkdf" @@ -223,7 +223,7 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati if req == nil { return } - req.Header.Set("User-Agent", http_proto.ChromeUA()) + req.Header.Set("User-Agent", utils.ChromeUA) if first && config.Show { fmt.Printf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent()) } diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index 9ecd5a436503..189e2efeb0fd 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -6,6 +6,7 @@ import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/crypto" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet" ) @@ -44,9 +45,12 @@ func (c *Config) GetNormalizedQuery() string { func (c *Config) GetRequestHeader() http.Header { header := http.Header{} - for k, v := range c.Headers { +for k, v := range c.Headers { header.Add(k, v) } + if header.Get("User-Agent") == "" { + header.Set("User-Agent", utils.ChromeUA) + } return header } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 25c2295194a7..6ba38aeadd0e 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -27,7 +27,6 @@ import ( "github.com/xtls/reality/hpke" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" - http_proto "github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet" "golang.org/x/crypto/cryptobyte" @@ -258,7 +257,7 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b } req.Header.Set("Accept", "application/dns-message") req.Header.Set("Content-Type", "application/dns-message") - req.Header.Set("User-Agent", http_proto.ChromeUA()) + req.Header.Set("User-Agent", utils.ChromeUA) req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) resp, err := client.Do(req) diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 4f2c0a14dd58..f3d5f71bf67b 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet" ) @@ -20,9 +21,12 @@ func (c *Config) GetNormalizedPath() string { func (c *Config) GetRequestHeader() http.Header { header := http.Header{} - for k, v := range c.Header { +for k, v := range c.Header { header.Add(k, v) } + if header.Get("User-Agent") == "" { + header.Set("User-Agent", utils.ChromeUA) + } return header } From f0021d65f644f43d4aa9de57d2da8b411cbb84fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:04:07 +0000 Subject: [PATCH 05/28] fix: apply gofmt and remove redundant check in grpc dial Co-authored-by: Fangliding <63339210+Fangliding@users.noreply.github.com> --- common/utils/browser.go | 16 ++++++++-------- transport/internet/grpc/dial.go | 6 ++---- transport/internet/httpupgrade/dialer.go | 4 ++-- transport/internet/splithttp/config.go | 2 +- transport/internet/websocket/config.go | 2 +- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 7f8c416592e4..80f68d5329d2 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -1,17 +1,17 @@ package utils import ( -"strconv" -"time" + "strconv" + "time" ) func browserVersion() int { -now := time.Now() -ver := 143 + (now.Year()-2026)*12 + int(now.Month()) - 1 -if ver < 143 { -ver = 143 -} -return ver + now := time.Now() + ver := 143 + (now.Year()-2026)*12 + int(now.Month()) - 1 + if ver < 143 { + ver = 143 + } + return ver } // ChromeUA provides default browser User-Agent. Version 143 = Jan 2026, +1 per month. diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index bb426351bee0..e0b0aa03f8a7 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -6,11 +6,11 @@ import ( "time" "github.com/xtls/xray-core/common" - "github.com/xtls/xray-core/common/utils" c "github.com/xtls/xray-core/common/ctx" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/grpc/encoding" "github.com/xtls/xray-core/transport/internet/reality" @@ -172,9 +172,7 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in if userAgent == "" { userAgent = utils.ChromeUA } - if userAgent != "" { - dialOptions = append(dialOptions, grpc.WithUserAgent(userAgent)) - } + dialOptions = append(dialOptions, grpc.WithUserAgent(userAgent)) var grpcDestHost string if dest.Address.Family().IsDomain() { diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index 1a5c758d32a3..4d718eb8036e 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/xtls/xray-core/common" - "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" @@ -87,7 +87,7 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * for key, value := range transportConfiguration.Header { AddHeader(req.Header, key, value) } -if req.Header.Get("User-Agent") == "" { + if req.Header.Get("User-Agent") == "" { req.Header.Set("User-Agent", utils.ChromeUA) } req.Header.Set("Connection", "Upgrade") diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index 189e2efeb0fd..1819f0e9daef 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -45,7 +45,7 @@ func (c *Config) GetNormalizedQuery() string { func (c *Config) GetRequestHeader() http.Header { header := http.Header{} -for k, v := range c.Headers { + for k, v := range c.Headers { header.Add(k, v) } if header.Get("User-Agent") == "" { diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index f3d5f71bf67b..bd38cd4fe837 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -21,7 +21,7 @@ func (c *Config) GetNormalizedPath() string { func (c *Config) GetRequestHeader() http.Header { header := http.Header{} -for k, v := range c.Header { + for k, v := range c.Header { header.Add(k, v) } if header.Get("User-Agent") == "" { From 0bb84811d4cefbb54eee527ba223444bcc4e5133 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:10:00 +0000 Subject: [PATCH 06/28] feat: add User-Agent to HTTP proxy client and regular observatory Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- app/observatory/observer.go | 5 ++++- proxy/http/client.go | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/observatory/observer.go b/app/observatory/observer.go index a6fba3adb445..0ff9ba64e078 100644 --- a/app/observatory/observer.go +++ b/app/observatory/observer.go @@ -15,6 +15,7 @@ import ( "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/task" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/extension" "github.com/xtls/xray-core/features/outbound" @@ -162,7 +163,9 @@ func (o *Observer) probe(outbound string) ProbeResult { if o.config.ProbeUrl != "" { probeURL = o.config.ProbeUrl } - response, err := httpClient.Get(probeURL) + req, _ := http.NewRequest(http.MethodGet, probeURL, nil) + req.Header.Set("User-Agent", utils.ChromeUA) + response, err := httpClient.Do(req) if err != nil { return errors.New("outbound failed to relay connection").Base(err) } diff --git a/proxy/http/client.go b/proxy/http/client.go index 1f804d0bb7d8..365d13bfaaca 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -21,6 +21,7 @@ import ( "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/task" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/transport" @@ -220,6 +221,10 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u req.Header.Set(h.Key, h.Value) } + if req.Header.Get("User-Agent") == "" { + req.Header.Set("User-Agent", utils.ChromeUA) + } + connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { req.Header.Set("Proxy-Connection", "Keep-Alive") From e3fb26773e4732665907fd5c6b4220ddfe8d1e01 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:11:49 +0000 Subject: [PATCH 07/28] refactor: rename browserVersion() to ChromeVersion() Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 80f68d5329d2..9de6f64a749e 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -5,7 +5,7 @@ import ( "time" ) -func browserVersion() int { +func ChromeVersion() int { now := time.Now() ver := 143 + (now.Year()-2026)*12 + int(now.Month()) - 1 if ver < 143 { @@ -15,4 +15,4 @@ func browserVersion() int { } // ChromeUA provides default browser User-Agent. Version 143 = Jan 2026, +1 per month. -var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(browserVersion()) + ".0.0.0 Safari/537.36" +var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(ChromeVersion()) + ".0.0.0 Safari/537.36" From c661bc1c2ae4b1f9694b52214339b6fb4e3c2a06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:56:11 +0000 Subject: [PATCH 08/28] fix: remove extra blank lines in burst/ping.go and http/client.go Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- app/observatory/burst/ping.go | 3 +-- proxy/http/client.go | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/observatory/burst/ping.go b/app/observatory/burst/ping.go index f469643841bc..f08adb571e3d 100644 --- a/app/observatory/burst/ping.go +++ b/app/observatory/burst/ping.go @@ -6,9 +6,8 @@ import ( "net/http" "time" - "github.com/xtls/xray-core/common/utils" - "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet/tagged" ) diff --git a/proxy/http/client.go b/proxy/http/client.go index 365d13bfaaca..f79ce5473740 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -220,7 +220,6 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u for _, h := range header { req.Header.Set(h.Key, h.Value) } - if req.Header.Get("User-Agent") == "" { req.Header.Set("User-Agent", utils.ChromeUA) } From 79d0d3f184f40de0d4ae5018517e0c00cf94ad5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:15:14 +0000 Subject: [PATCH 09/28] feat: add version variation to reduce statistical fingerprinting Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/utils/browser.go b/common/utils/browser.go index 9de6f64a749e..aa54e1daa447 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -3,6 +3,8 @@ package utils import ( "strconv" "time" + + "github.com/xtls/xray-core/core" ) func ChromeVersion() int { @@ -11,6 +13,14 @@ func ChromeVersion() int { if ver < 143 { ver = 143 } + // Add variation based on core version to reduce statistical fingerprinting + if (core.Version_x+core.Version_y+core.Version_z)%2 == 1 { + if now.Day() < 15 { + ver-- + } else { + ver++ + } + } return ver } From c3796720b6485c16f310b36af54ed8005b6a5975 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:27:15 +0000 Subject: [PATCH 10/28] feat: distribute version transition uniformly across days 15-30 Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index aa54e1daa447..0fca0a495c6f 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -13,13 +13,10 @@ func ChromeVersion() int { if ver < 143 { ver = 143 } - // Add variation based on core version to reduce statistical fingerprinting - if (core.Version_x+core.Version_y+core.Version_z)%2 == 1 { - if now.Day() < 15 { - ver-- - } else { - ver++ - } + // Distribute version transition across days 15-30 based on core version + transitionDay := 15 + int(core.Version_x+core.Version_y+core.Version_z)%16 + if now.Day() >= transitionDay { + ver++ } return ver } From 87df4c9ae9ad36283920c3ad7e4426a252df7b10 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:30:06 +0000 Subject: [PATCH 11/28] fix: use actual last day of month for version transition distribution Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 0fca0a495c6f..e50b8ffb2f6e 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -13,8 +13,10 @@ func ChromeVersion() int { if ver < 143 { ver = 143 } - // Distribute version transition across days 15-30 based on core version - transitionDay := 15 + int(core.Version_x+core.Version_y+core.Version_z)%16 + // Distribute version transition across days 15 to end of month based on core version + lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, time.Local).Day() + daysRange := lastDay - 14 // days from 15 to lastDay + transitionDay := 15 + int(core.Version_x+core.Version_y+core.Version_z)%daysRange if now.Day() >= transitionDay { ver++ } From 7270e09f47e42711b2c780494949bad727b952d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:35:01 +0000 Subject: [PATCH 12/28] feat: distribute version -1 uniformly before day 15 as well Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index e50b8ffb2f6e..bf719340951e 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -13,12 +13,22 @@ func ChromeVersion() int { if ver < 143 { ver = 143 } - // Distribute version transition across days 15 to end of month based on core version - lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, time.Local).Day() - daysRange := lastDay - 14 // days from 15 to lastDay - transitionDay := 15 + int(core.Version_x+core.Version_y+core.Version_z)%daysRange - if now.Day() >= transitionDay { - ver++ + versionSum := int(core.Version_x + core.Version_y + core.Version_z) + day := now.Day() + if day < 15 { + // Days 1-14: distribute -1 transition uniformly + transitionDay := 1 + versionSum%14 + if day >= transitionDay { + ver-- + } + } else { + // Days 15 to end of month: distribute +1 transition uniformly + lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, time.Local).Day() + daysRange := lastDay - 14 + transitionDay := 15 + versionSum%daysRange + if day >= transitionDay { + ver++ + } } return ver } From 91e1c4f41c25572dad74915ca008f81d3786410d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:37:45 +0000 Subject: [PATCH 13/28] feat: distribute boundary between -1/+1 zones across days 15-20 Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index bf719340951e..7a4ed948d9bc 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -15,17 +15,19 @@ func ChromeVersion() int { } versionSum := int(core.Version_x + core.Version_y + core.Version_z) day := now.Day() - if day < 15 { - // Days 1-14: distribute -1 transition uniformly - transitionDay := 1 + versionSum%14 + // Boundary between -1 and +1 zones is distributed between days 15-20 + boundary := 15 + versionSum%6 + if day < boundary { + // Before boundary: distribute -1 transition uniformly from day 1 to boundary-1 + transitionDay := 1 + versionSum%(boundary-1) if day >= transitionDay { ver-- } } else { - // Days 15 to end of month: distribute +1 transition uniformly + // From boundary to end of month: distribute +1 transition uniformly lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, time.Local).Day() - daysRange := lastDay - 14 - transitionDay := 15 + versionSum%daysRange + daysRange := lastDay - boundary + 1 + transitionDay := boundary + versionSum%daysRange if day >= transitionDay { ver++ } From d247358b34e75e6971decf743c21217a5298a9f6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:43:19 +0000 Subject: [PATCH 14/28] feat: add CPU ID to seed for machine-specific distribution Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 7a4ed948d9bc..14437e0a4a26 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -4,6 +4,7 @@ import ( "strconv" "time" + "github.com/klauspost/cpuid/v2" "github.com/xtls/xray-core/core" ) @@ -13,13 +14,14 @@ func ChromeVersion() int { if ver < 143 { ver = 143 } - versionSum := int(core.Version_x + core.Version_y + core.Version_z) + // Combine core version with CPU ID for machine-specific distribution + seed := int(core.Version_x+core.Version_y+core.Version_z) + cpuid.CPU.Family + cpuid.CPU.Model day := now.Day() // Boundary between -1 and +1 zones is distributed between days 15-20 - boundary := 15 + versionSum%6 + boundary := 15 + seed%6 if day < boundary { // Before boundary: distribute -1 transition uniformly from day 1 to boundary-1 - transitionDay := 1 + versionSum%(boundary-1) + transitionDay := 1 + seed%(boundary-1) if day >= transitionDay { ver-- } @@ -27,7 +29,7 @@ func ChromeVersion() int { // From boundary to end of month: distribute +1 transition uniformly lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, time.Local).Day() daysRange := lastDay - boundary + 1 - transitionDay := boundary + versionSum%daysRange + transitionDay := boundary + seed%daysRange if day >= transitionDay { ver++ } From 83fcb8b3f3ad71e01b97017fe55aa171109893a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:00:17 +0000 Subject: [PATCH 15/28] chore: go mod tidy - cpuid is now a direct dependency Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 77d8780c3560..4a97fead906a 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/golang/mock v1.7.0-rc.1 github.com/google/go-cmp v0.7.0 github.com/gorilla/websocket v1.5.3 + github.com/klauspost/cpuid/v2 v2.0.12 github.com/miekg/dns v1.1.72 github.com/pelletier/go-toml v1.9.5 github.com/pires/go-proxyproto v0.9.2 @@ -39,7 +40,6 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/juju/ratelimit v1.0.2 // indirect github.com/klauspost/compress v1.17.4 // indirect - github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect From 4c77ae8c214951e4ae8f5e882d7c40921a87adfb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:10:37 +0000 Subject: [PATCH 16/28] feat: simplify to 1/3 probability each for -1/0/+1 version adjustment Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 14437e0a4a26..edc01fdeba26 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -14,25 +14,14 @@ func ChromeVersion() int { if ver < 143 { ver = 143 } - // Combine core version with CPU ID for machine-specific distribution + // Combine core version with CPU ID for machine-specific seed seed := int(core.Version_x+core.Version_y+core.Version_z) + cpuid.CPU.Family + cpuid.CPU.Model - day := now.Day() - // Boundary between -1 and +1 zones is distributed between days 15-20 - boundary := 15 + seed%6 - if day < boundary { - // Before boundary: distribute -1 transition uniformly from day 1 to boundary-1 - transitionDay := 1 + seed%(boundary-1) - if day >= transitionDay { - ver-- - } - } else { - // From boundary to end of month: distribute +1 transition uniformly - lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, time.Local).Day() - daysRange := lastDay - boundary + 1 - transitionDay := boundary + seed%daysRange - if day >= transitionDay { - ver++ - } + // 1/3 probability each for -1, 0, +1 based on seed % 3 + switch seed % 3 { + case 0: + ver-- + case 2: + ver++ } return ver } From 5d873b54320ca7df97a699a4096987335216a414 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:15:58 +0000 Subject: [PATCH 17/28] fix: restore boundary logic, only apply 1/3 probability after boundary Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index edc01fdeba26..71fde62896de 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -16,12 +16,22 @@ func ChromeVersion() int { } // Combine core version with CPU ID for machine-specific seed seed := int(core.Version_x+core.Version_y+core.Version_z) + cpuid.CPU.Family + cpuid.CPU.Model - // 1/3 probability each for -1, 0, +1 based on seed % 3 - switch seed % 3 { - case 0: - ver-- - case 2: - ver++ + // Boundary day uniformly distributed between 15-20 based on seed + boundary := 15 + seed%6 + day := now.Day() + if day < boundary { + // Before boundary: uniformly distributed -1 within days 1 to boundary-1 + if seed%(boundary-1) < day { + ver-- + } + } else { + // After boundary: 1/3 probability each for -1, 0, +1 + switch seed % 3 { + case 0: + ver-- + case 2: + ver++ + } } return ver } From 82b67999426e490cacbc2b50f5dbf3191bd0727b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:24:28 +0000 Subject: [PATCH 18/28] feat: use CPU features as seed, avoid downgrade after boundary Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 71fde62896de..70919e4170ab 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -5,7 +5,6 @@ import ( "time" "github.com/klauspost/cpuid/v2" - "github.com/xtls/xray-core/core" ) func ChromeVersion() int { @@ -14,8 +13,8 @@ func ChromeVersion() int { if ver < 143 { ver = 143 } - // Combine core version with CPU ID for machine-specific seed - seed := int(core.Version_x+core.Version_y+core.Version_z) + cpuid.CPU.Family + cpuid.CPU.Model + // Use CPU features as machine-specific seed + seed := cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine // Boundary day uniformly distributed between 15-20 based on seed boundary := 15 + seed%6 day := now.Day() @@ -25,12 +24,22 @@ func ChromeVersion() int { ver-- } } else { - // After boundary: 1/3 probability each for -1, 0, +1 - switch seed % 3 { - case 0: - ver-- - case 2: - ver++ + // After boundary: avoid downgrade + // If seed % 3 == 0, it would have gotten -1 before boundary, so allow -1/0/+1 + // Otherwise, it didn't get -1 before, so only allow 0/+1 + if seed%3 == 0 { + // Was -1 before boundary, can be -1/0/+1 after + switch seed % 3 { + case 0: + ver-- + case 2: + ver++ + } + } else { + // Was not -1 before boundary, can only be 0/+1 after + if seed%2 == 1 { + ver++ + } } } return ver From 9f92e5b21cbbe763e3031d680179a09011511243 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:48:50 +0000 Subject: [PATCH 19/28] feat: re-add x+y+z to seed, restore original 1/3 strategy after boundary Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 70919e4170ab..ce59604aba71 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -5,6 +5,7 @@ import ( "time" "github.com/klauspost/cpuid/v2" + "github.com/xtls/xray-core/core" ) func ChromeVersion() int { @@ -13,8 +14,8 @@ func ChromeVersion() int { if ver < 143 { ver = 143 } - // Use CPU features as machine-specific seed - seed := cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine + // Use CPU features + Xray version as seed (version changes ensure occasional different "upgrade strategies") + seed := cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine + int(core.Version_x) + int(core.Version_y) + int(core.Version_z) // Boundary day uniformly distributed between 15-20 based on seed boundary := 15 + seed%6 day := now.Day() @@ -24,22 +25,12 @@ func ChromeVersion() int { ver-- } } else { - // After boundary: avoid downgrade - // If seed % 3 == 0, it would have gotten -1 before boundary, so allow -1/0/+1 - // Otherwise, it didn't get -1 before, so only allow 0/+1 - if seed%3 == 0 { - // Was -1 before boundary, can be -1/0/+1 after - switch seed % 3 { - case 0: - ver-- - case 2: - ver++ - } - } else { - // Was not -1 before boundary, can only be 0/+1 after - if seed%2 == 1 { - ver++ - } + // After boundary: 1/3 probability each for -1/0/+1 + switch seed % 3 { + case 0: + ver-- + case 2: + ver++ } } return ver From 169618a13c829d6d9542fd476774cc7a31629f9a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:51:34 +0000 Subject: [PATCH 20/28] feat: change post-boundary to uniformly distributed +1 instead of 1/3 Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index ce59604aba71..a540e28fde11 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -25,11 +25,10 @@ func ChromeVersion() int { ver-- } } else { - // After boundary: 1/3 probability each for -1/0/+1 - switch seed % 3 { - case 0: - ver-- - case 2: + // After boundary: uniformly distributed whether +1 + lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, now.Location()).Day() + daysAfter := lastDay - boundary + 1 + if seed%daysAfter < day-boundary+1 { ver++ } } From cb325e51eacde26618c52e91ba33d3af764f02ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:59:54 +0000 Subject: [PATCH 21/28] refactor: simplify post-boundary +1 to intuitive 50% chance based on seed Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index a540e28fde11..29fc005f32f8 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -25,10 +25,8 @@ func ChromeVersion() int { ver-- } } else { - // After boundary: uniformly distributed whether +1 - lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, now.Location()).Day() - daysAfter := lastDay - boundary + 1 - if seed%daysAfter < day-boundary+1 { + // After boundary: 50% chance to +1 based on seed + if seed%2 == 1 { ver++ } } From 791b96b54a5ef5fa67c47c4240d38f097f334dd7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:03:30 +0000 Subject: [PATCH 22/28] feat: true uniform distribution for post-boundary +1 based on days in range Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 29fc005f32f8..d9589d59c1fc 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -25,8 +25,10 @@ func ChromeVersion() int { ver-- } } else { - // After boundary: 50% chance to +1 based on seed - if seed%2 == 1 { + // After boundary: uniformly distributed +1 within days boundary to lastDay + lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, time.UTC).Day() + daysInRange := lastDay - boundary + 1 + if seed%daysInRange < day-boundary+1 { ver++ } } From 2259d53aeb25d0b4c3277fc83043082669d94dbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:21:05 +0000 Subject: [PATCH 23/28] feat: subtract baseVersion mod 2 to avoid month-end/start version flipping Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index d9589d59c1fc..2e9d7b672886 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -10,10 +10,11 @@ import ( func ChromeVersion() int { now := time.Now() - ver := 143 + (now.Year()-2026)*12 + int(now.Month()) - 1 - if ver < 143 { - ver = 143 + baseVer := 143 + (now.Year()-2026)*12 + int(now.Month()) - 1 + if baseVer < 143 { + baseVer = 143 } + ver := baseVer // Use CPU features + Xray version as seed (version changes ensure occasional different "upgrade strategies") seed := cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine + int(core.Version_x) + int(core.Version_y) + int(core.Version_z) // Boundary day uniformly distributed between 15-20 based on seed @@ -32,7 +33,8 @@ func ChromeVersion() int { ver++ } } - return ver + // Subtract base version mod 2 to avoid month-end upgrade followed by month-start downgrade + return ver - baseVer%2 } // ChromeUA provides default browser User-Agent. Version 143 = Jan 2026, +1 per month. From 429fac8092945ca70690ac50a5dff9fc42adc2de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:41:06 +0000 Subject: [PATCH 24/28] feat: rewrite ChromeVersion with new strategy - release on 15th, 1-30 day upgrade delay Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 43 +++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 2e9d7b672886..be2858d0c992 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -10,32 +10,29 @@ import ( func ChromeVersion() int { now := time.Now() - baseVer := 143 + (now.Year()-2026)*12 + int(now.Month()) - 1 - if baseVer < 143 { - baseVer = 143 + // Assume Chrome releases on 15th each month, with Jan 2026 being version 144 + // Calculate the latest Chrome version based on current date + latestVer := 144 + (now.Year()-2026)*12 + int(now.Month()) - 1 + daysSinceRelease := now.Day() - 15 + if daysSinceRelease < 0 { + latestVer-- // Before 15th, the new version hasn't released yet + // Days since previous month's release (15th) + lastDay := time.Date(now.Year(), now.Month(), 0, 0, 0, 0, 0, time.UTC).Day() + daysSinceRelease = now.Day() + (lastDay - 15) } - ver := baseVer - // Use CPU features + Xray version as seed (version changes ensure occasional different "upgrade strategies") + if latestVer < 144 { + latestVer = 144 + } + // Use CPU features + Xray version as seed seed := cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine + int(core.Version_x) + int(core.Version_y) + int(core.Version_z) - // Boundary day uniformly distributed between 15-20 based on seed - boundary := 15 + seed%6 - day := now.Day() - if day < boundary { - // Before boundary: uniformly distributed -1 within days 1 to boundary-1 - if seed%(boundary-1) < day { - ver-- - } - } else { - // After boundary: uniformly distributed +1 within days boundary to lastDay - lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, time.UTC).Day() - daysInRange := lastDay - boundary + 1 - if seed%daysInRange < day-boundary+1 { - ver++ - } + // User upgrade delay: 1-30 days uniformly distributed based on seed + upgradeDelay := seed%30 + 1 + // If user's delay > days since release, they haven't upgraded yet + if upgradeDelay > daysSinceRelease { + return latestVer - 1 } - // Subtract base version mod 2 to avoid month-end upgrade followed by month-start downgrade - return ver - baseVer%2 + return latestVer } -// ChromeUA provides default browser User-Agent. Version 143 = Jan 2026, +1 per month. +// ChromeUA provides default browser User-Agent. Chrome 144 = Jan 15, 2026, +1 per month. var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(ChromeVersion()) + ".0.0.0 Safari/537.36" From 0d0e65df6b1eda97809962bf029790f3d9aa48a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:48:21 +0000 Subject: [PATCH 25/28] refactor: simplify ChromeVersion logic - subtract upgrade delay from current date Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index be2858d0c992..736bb3e47739 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -10,28 +10,25 @@ import ( func ChromeVersion() int { now := time.Now() - // Assume Chrome releases on 15th each month, with Jan 2026 being version 144 - // Calculate the latest Chrome version based on current date + // Chrome releases on 15th each month: 2026.1.15 = 144 + // Calculate the latest Chrome version at the 15th of current month latestVer := 144 + (now.Year()-2026)*12 + int(now.Month()) - 1 - daysSinceRelease := now.Day() - 15 - if daysSinceRelease < 0 { - latestVer-- // Before 15th, the new version hasn't released yet - // Days since previous month's release (15th) - lastDay := time.Date(now.Year(), now.Month(), 0, 0, 0, 0, 0, time.UTC).Day() - daysSinceRelease = now.Day() + (lastDay - 15) - } - if latestVer < 144 { - latestVer = 144 - } - // Use CPU features + Xray version as seed + // Use CPU features + Xray version as seed for upgrade delay (1-30 days) seed := cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine + int(core.Version_x) + int(core.Version_y) + int(core.Version_z) - // User upgrade delay: 1-30 days uniformly distributed based on seed upgradeDelay := seed%30 + 1 - // If user's delay > days since release, they haven't upgraded yet - if upgradeDelay > daysSinceRelease { - return latestVer - 1 + // Subtract upgrade delay from current date to get user's Chrome version date + userDate := now.AddDate(0, 0, -upgradeDelay) + userVer := 144 + (userDate.Year()-2026)*12 + int(userDate.Month()) - 1 + if userDate.Day() < 15 { + userVer-- + } + if userVer < 144 { + userVer = 144 + } + if userVer > latestVer { + userVer = latestVer } - return latestVer + return userVer } // ChromeUA provides default browser User-Agent. Chrome 144 = Jan 15, 2026, +1 per month. From 596c3e463d45d2c486d45cb2274d6bfc584ef2c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:00:01 +0000 Subject: [PATCH 26/28] refactor: simplify ChromeVersion - subtract delay first, then calculate version Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 736bb3e47739..9e511f1d098d 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -9,26 +9,17 @@ import ( ) func ChromeVersion() int { - now := time.Now() - // Chrome releases on 15th each month: 2026.1.15 = 144 - // Calculate the latest Chrome version at the 15th of current month - latestVer := 144 + (now.Year()-2026)*12 + int(now.Month()) - 1 // Use CPU features + Xray version as seed for upgrade delay (1-30 days) seed := cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine + int(core.Version_x) + int(core.Version_y) + int(core.Version_z) upgradeDelay := seed%30 + 1 - // Subtract upgrade delay from current date to get user's Chrome version date - userDate := now.AddDate(0, 0, -upgradeDelay) - userVer := 144 + (userDate.Year()-2026)*12 + int(userDate.Month()) - 1 + // First subtract upgrade delay from current date + userDate := time.Now().AddDate(0, 0, -upgradeDelay) + // Then calculate Chrome version for that date (releases on 15th each month, 2026.1.15 = 144) + version := 144 + (userDate.Year()-2026)*12 + int(userDate.Month()) - 1 if userDate.Day() < 15 { - userVer-- + version-- } - if userVer < 144 { - userVer = 144 - } - if userVer > latestVer { - userVer = latestVer - } - return userVer + return version } // ChromeUA provides default browser User-Agent. Chrome 144 = Jan 15, 2026, +1 per month. From be9e60ab0c46c22e43d9a189f39640ea2919e39f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:13:55 +0000 Subject: [PATCH 27/28] feat: change to 45-day release cycle based on Chrome 144 release date (2026.1.13) Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 9e511f1d098d..eaa964521e36 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -9,18 +9,17 @@ import ( ) func ChromeVersion() int { - // Use CPU features + Xray version as seed for upgrade delay (1-30 days) + // Chrome 144 released on 2026.1.13, new version every 45 days + chrome144Release := time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC) + // Use CPU features + Xray version as seed for upgrade delay (1-45 days) seed := cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine + int(core.Version_x) + int(core.Version_y) + int(core.Version_z) - upgradeDelay := seed%30 + 1 + upgradeDelay := seed%45 + 1 // First subtract upgrade delay from current date userDate := time.Now().AddDate(0, 0, -upgradeDelay) - // Then calculate Chrome version for that date (releases on 15th each month, 2026.1.15 = 144) - version := 144 + (userDate.Year()-2026)*12 + int(userDate.Month()) - 1 - if userDate.Day() < 15 { - version-- - } - return version + // Then calculate Chrome version for that date + daysSinceRelease := int(userDate.Sub(chrome144Release).Hours() / 24) + return 144 + daysSinceRelease/45 } -// ChromeUA provides default browser User-Agent. Chrome 144 = Jan 15, 2026, +1 per month. +// ChromeUA provides default browser User-Agent. Chrome 144 = Jan 13, 2026, +1 per 45 days. var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(ChromeVersion()) + ".0.0.0 Safari/537.36" From 1427b2098f42215f2d3fc614630e98dd3553fb32 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:45:42 +0000 Subject: [PATCH 28/28] feat: new strategy with PRNG seeded by CPU, random 25-45 day intervals from 2026.1.13 Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- common/utils/browser.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index eaa964521e36..91209f4bda50 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -1,25 +1,28 @@ package utils import ( + "math/rand" "strconv" "time" "github.com/klauspost/cpuid/v2" - "github.com/xtls/xray-core/core" ) func ChromeVersion() int { - // Chrome 144 released on 2026.1.13, new version every 45 days - chrome144Release := time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC) - // Use CPU features + Xray version as seed for upgrade delay (1-45 days) - seed := cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine + int(core.Version_x) + int(core.Version_y) + int(core.Version_z) - upgradeDelay := seed%45 + 1 - // First subtract upgrade delay from current date - userDate := time.Now().AddDate(0, 0, -upgradeDelay) - // Then calculate Chrome version for that date - daysSinceRelease := int(userDate.Sub(chrome144Release).Hours() / 24) - return 144 + daysSinceRelease/45 + // Use only CPU info as seed for PRNG + seed := int64(cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine) + rng := rand.New(rand.NewSource(seed)) + // Start from Chrome 144 released on 2026.1.13 + releaseDate := time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC) + version := 144 + now := time.Now() + // Each version has random 25-45 day interval + for releaseDate.Before(now) { + releaseDate = releaseDate.AddDate(0, 0, rng.Intn(21)+25) + version++ + } + return version - 1 } -// ChromeUA provides default browser User-Agent. Chrome 144 = Jan 13, 2026, +1 per 45 days. +// ChromeUA provides default browser User-Agent based on CPU-seeded PRNG. var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(ChromeVersion()) + ".0.0.0 Safari/537.36"