Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a11b756
Initial plan
Copilot Feb 5, 2026
06fd58d
feat: set default User-Agent to Chrome with dynamic version based on …
Copilot Feb 5, 2026
b61d737
fix: add minimum version constraint for ChromeUA and improve test
Copilot Feb 5, 2026
a7c0037
refactor: move ChromeUA to utils as var, add UA to XHTTP/WS/HTTPUpgra…
Copilot Feb 5, 2026
f0021d6
fix: apply gofmt and remove redundant check in grpc dial
Copilot Feb 5, 2026
0bb8481
feat: add User-Agent to HTTP proxy client and regular observatory
Copilot Feb 5, 2026
e3fb267
refactor: rename browserVersion() to ChromeVersion()
Copilot Feb 5, 2026
c661bc1
fix: remove extra blank lines in burst/ping.go and http/client.go
Copilot Feb 5, 2026
79d0d3f
feat: add version variation to reduce statistical fingerprinting
Copilot Feb 5, 2026
c379672
feat: distribute version transition uniformly across days 15-30
Copilot Feb 5, 2026
87df4c9
fix: use actual last day of month for version transition distribution
Copilot Feb 5, 2026
7270e09
feat: distribute version -1 uniformly before day 15 as well
Copilot Feb 5, 2026
91e1c4f
feat: distribute boundary between -1/+1 zones across days 15-20
Copilot Feb 5, 2026
d247358
feat: add CPU ID to seed for machine-specific distribution
Copilot Feb 5, 2026
83fcb8b
chore: go mod tidy - cpuid is now a direct dependency
Copilot Feb 5, 2026
4c77ae8
feat: simplify to 1/3 probability each for -1/0/+1 version adjustment
Copilot Feb 5, 2026
5d873b5
fix: restore boundary logic, only apply 1/3 probability after boundary
Copilot Feb 5, 2026
82b6799
feat: use CPU features as seed, avoid downgrade after boundary
Copilot Feb 5, 2026
9f92e5b
feat: re-add x+y+z to seed, restore original 1/3 strategy after boundary
Copilot Feb 5, 2026
169618a
feat: change post-boundary to uniformly distributed +1 instead of 1/3
Copilot Feb 5, 2026
cb325e5
refactor: simplify post-boundary +1 to intuitive 50% chance based on …
Copilot Feb 5, 2026
791b96b
feat: true uniform distribution for post-boundary +1 based on days in…
Copilot Feb 5, 2026
2259d53
feat: subtract baseVersion mod 2 to avoid month-end/start version fli…
Copilot Feb 5, 2026
429fac8
feat: rewrite ChromeVersion with new strategy - release on 15th, 1-30…
Copilot Feb 5, 2026
0d0e65d
refactor: simplify ChromeVersion logic - subtract upgrade delay from …
Copilot Feb 5, 2026
596c3e4
refactor: simplify ChromeVersion - subtract delay first, then calcula…
Copilot Feb 6, 2026
be9e60a
feat: change to 45-day release cycle based on Chrome 144 release date…
Copilot Feb 6, 2026
1427b20
feat: new strategy with PRNG seeded by CPU, random 25-45 day interval…
Copilot Feb 6, 2026
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
1 change: 1 addition & 0 deletions app/dns/nameserver_doh.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +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", utils.ChromeUA)
req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000)))

hc := s.httpClient
Expand Down
2 changes: 2 additions & 0 deletions app/observatory/burst/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"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"
)
Expand Down Expand Up @@ -61,6 +62,7 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) {
if err != nil {
return rttFailed, err
}
req.Header.Set("User-Agent", utils.ChromeUA)

start := time.Now()
resp, err := s.httpClient.Do(req)
Expand Down
5 changes: 4 additions & 1 deletion app/observatory/observer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down
28 changes: 28 additions & 0 deletions common/utils/browser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package utils

import (
"math/rand"
"strconv"
"time"

"github.com/klauspost/cpuid/v2"
)

func ChromeVersion() int {
// 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 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"
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions infra/conf/transport_authenticators.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"sort"

"github.com/xtls/xray-core/common/errors"
"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"
Expand Down Expand Up @@ -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{utils.ChromeUA},
},
{
Name: "Accept-Encoding",
Expand Down
4 changes: 4 additions & 0 deletions proxy/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -219,6 +220,9 @@ 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)
}

connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) {
req.Header.Set("Proxy-Connection", "Keep-Alive")
Expand Down
7 changes: 5 additions & 2 deletions transport/internet/grpc/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"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"
Expand Down Expand Up @@ -167,9 +168,11 @@ 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
}
dialOptions = append(dialOptions, grpc.WithUserAgent(userAgent))

var grpcDestHost string
if dest.Address.Family().IsDomain() {
Expand Down
4 changes: 4 additions & 0 deletions transport/internet/httpupgrade/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/xtls/xray-core/common"
"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"
Expand Down Expand Up @@ -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")

Expand Down
3 changes: 2 additions & 1 deletion transport/internet/reality/reality.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
"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"
Expand Down Expand Up @@ -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", utils.ChromeUA)
if first && config.Show {
fmt.Printf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent())
}
Expand Down
4 changes: 4 additions & 0 deletions transport/internet/splithttp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -47,6 +48,9 @@ func (c *Config) GetRequestHeader() http.Header {
for k, v := range c.Headers {
header.Add(k, v)
}
if header.Get("User-Agent") == "" {
header.Set("User-Agent", utils.ChromeUA)
}
return header
}

Expand Down
1 change: 1 addition & 0 deletions transport/internet/tls/ech.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +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", utils.ChromeUA)
req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000)))

resp, err := client.Do(req)
Expand Down
4 changes: 4 additions & 0 deletions transport/internet/websocket/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -23,6 +24,9 @@ func (c *Config) GetRequestHeader() http.Header {
for k, v := range c.Header {
header.Add(k, v)
}
if header.Get("User-Agent") == "" {
header.Set("User-Agent", utils.ChromeUA)
}
return header
}

Expand Down