From 1d1119b76c07bf39394935412c5035ea4a3e0476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 10:32:03 +0000 Subject: [PATCH 01/53] Added a new field generating valid Chrome UA CH. --- common/utils/browser.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 91209f4bda50..76d98112163f 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -25,4 +25,13 @@ func ChromeVersion() int { } // 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" +var AnchoredChromeVersion = ChromeVersion() +var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36" + +func getValidManglingChar() string { + // Valid characters for the mangled Sec-CH-UA header + return string(" ,-_:;()"[rand.Int() & 7]); +} + +// It would be better to have the three parts ordered randomly upon generation +var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + ", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" From 2ca5615912611bc51ad95fd052355650344f0791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 10:40:48 +0000 Subject: [PATCH 02/53] Added the missing headers in the DoH context. --- app/dns/nameserver_doh.go | 7 +++++++ common/utils/browser.go | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go index 2126bcd85457..c32a82777046 100644 --- a/app/dns/nameserver_doh.go +++ b/app/dns/nameserver_doh.go @@ -215,6 +215,13 @@ 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("Sec-CH-UA", utils.ChromeUACH) + req.Header.Set("Sec-CH-UA-Mobile", "?0") + req.Header.Set("Sec-CH-UA-Platform", "Windows") + req.Header.Set("Sec-Fetch-Mode", "cors") // Vary! + req.Header.Set("Sec-Fetch-Site", "none") + req.Header.Set("Sec-Fetch-Dest", "empty") + req.Header.Set("Sec-Fetch-User", "?1") req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) hc := s.httpClient diff --git a/common/utils/browser.go b/common/utils/browser.go index 76d98112163f..06388f93aec5 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -34,4 +34,4 @@ func getValidManglingChar() string { } // It would be better to have the three parts ordered randomly upon generation -var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + ", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" +var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" From cfaebf00578f588450f854f49a528cfaec206fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 10:47:32 +0000 Subject: [PATCH 03/53] Extra Chrome headers in the observatory. --- app/dns/nameserver_doh.go | 2 +- app/observatory/burst/ping.go | 7 +++++++ app/observatory/observer.go | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go index c32a82777046..6978802bdb9a 100644 --- a/app/dns/nameserver_doh.go +++ b/app/dns/nameserver_doh.go @@ -219,8 +219,8 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, req.Header.Set("Sec-CH-UA-Mobile", "?0") req.Header.Set("Sec-CH-UA-Platform", "Windows") req.Header.Set("Sec-Fetch-Mode", "cors") // Vary! + req.Header.Set("Sec-Fetch-Dest", "empty") // Vary! req.Header.Set("Sec-Fetch-Site", "none") - req.Header.Set("Sec-Fetch-Dest", "empty") req.Header.Set("Sec-Fetch-User", "?1") req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) diff --git a/app/observatory/burst/ping.go b/app/observatory/burst/ping.go index f08adb571e3d..f03bfd4a3268 100644 --- a/app/observatory/burst/ping.go +++ b/app/observatory/burst/ping.go @@ -63,6 +63,13 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) { return rttFailed, err } req.Header.Set("User-Agent", utils.ChromeUA) + req.Header.Set("Sec-CH-UA", utils.ChromeUACH) + req.Header.Set("Sec-CH-UA-Mobile", "?0") + req.Header.Set("Sec-CH-UA-Platform", "Windows") + req.Header.Set("Sec-Fetch-Mode", "navigate") // Vary! + req.Header.Set("Sec-Fetch-Dest", "document") // Vary! + req.Header.Set("Sec-Fetch-Site", "none") + req.Header.Set("Sec-Fetch-User", "?1") start := time.Now() resp, err := s.httpClient.Do(req) diff --git a/app/observatory/observer.go b/app/observatory/observer.go index 0ff9ba64e078..168a7683311d 100644 --- a/app/observatory/observer.go +++ b/app/observatory/observer.go @@ -165,6 +165,13 @@ func (o *Observer) probe(outbound string) ProbeResult { } req, _ := http.NewRequest(http.MethodGet, probeURL, nil) req.Header.Set("User-Agent", utils.ChromeUA) + req.Header.Set("Sec-CH-UA", utils.ChromeUACH) + req.Header.Set("Sec-CH-UA-Mobile", "?0") + req.Header.Set("Sec-CH-UA-Platform", "Windows") + req.Header.Set("Sec-Fetch-Mode", "navigate") // Vary! + req.Header.Set("Sec-Fetch-Dest", "document") // Vary! + req.Header.Set("Sec-Fetch-Site", "none") + req.Header.Set("Sec-Fetch-User", "?1") response, err := httpClient.Do(req) if err != nil { return errors.New("outbound failed to relay connection").Base(err) From c8f0b35d13d274c0875ca44214a2c3b8adb3c262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 10:52:19 +0000 Subject: [PATCH 04/53] More Chrome headers in transport authenticators. --- infra/conf/transport_authenticators.go | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/infra/conf/transport_authenticators.go b/infra/conf/transport_authenticators.go index ea286f41d4a1..f019a4b08fcf 100644 --- a/infra/conf/transport_authenticators.go +++ b/infra/conf/transport_authenticators.go @@ -44,6 +44,34 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) { Name: "User-Agent", Value: []string{utils.ChromeUA}, }, + { + Name: "Sec-CH-UA", + Value: []strings{utils.ChromeUACH}, + }, + { + Name: "Sec-CH-UA-Mobile", + Value: []strings{"?0"}, + }, + { + Name: "Sec-CH-UA-Platform", + Value: []strings{"Windows"}, + }, + { + Name: "Sec-Fetch-Mode", + Value: []strings{"no-cors", "cors", "same-origin"}, + }, + { + Name: "Sec-Fetch-Dest", + Value: []strings{"empty"}, + }, + { + Name: "Sec-Fetch-Site", + Value: []strings{"none"}, + }, + { + Name: "Sec-Fetch-User", + Value: []strings{"?1"}, + }, { Name: "Accept-Encoding", Value: []string{"gzip, deflate"}, From 903150afbfaab7bd339b3c1d67cce02f7ff80c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 11:02:11 +0000 Subject: [PATCH 05/53] Remaining places to add headers. --- proxy/http/client.go | 7 +++++++ transport/internet/httpupgrade/dialer.go | 7 +++++++ transport/internet/reality/reality.go | 7 +++++++ transport/internet/splithttp/config.go | 7 +++++++ transport/internet/tls/ech.go | 7 +++++++ 5 files changed, 35 insertions(+) diff --git a/proxy/http/client.go b/proxy/http/client.go index f79ce5473740..4d3a90046955 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -222,6 +222,13 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u } if req.Header.Get("User-Agent") == "" { req.Header.Set("User-Agent", utils.ChromeUA) + req.Header.Set("Sec-CH-UA", utils.ChromeUACH) + req.Header.Set("Sec-CH-UA-Mobile", "?0") + req.Header.Set("Sec-CH-UA-Platform", "Windows") + req.Header.Set("Sec-Fetch-Mode", "cors") // Vary! + req.Header.Set("Sec-Fetch-Dest", "empty") // Vary! + req.Header.Set("Sec-Fetch-Site", "none") + req.Header.Set("Sec-Fetch-User", "?1") } connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index eacbded4edb2..71f2c5e7ee5e 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -98,6 +98,13 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * } if req.Header.Get("User-Agent") == "" { req.Header.Set("User-Agent", utils.ChromeUA) + req.Header.Set("Sec-CH-UA", utils.ChromeUACH) + req.Header.Set("Sec-CH-UA-Mobile", "?0") + req.Header.Set("Sec-CH-UA-Platform", "Windows") + req.Header.Set("Sec-Fetch-Mode", "cors") // Vary! + req.Header.Set("Sec-Fetch-Dest", "empty") // Vary! + req.Header.Set("Sec-Fetch-Site", "none") + req.Header.Set("Sec-Fetch-User", "?1") } 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 1f6de2b5b17b..0449f41da2bb 100644 --- a/transport/internet/reality/reality.go +++ b/transport/internet/reality/reality.go @@ -224,6 +224,13 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati return } req.Header.Set("User-Agent", utils.ChromeUA) + req.Header.Set("Sec-CH-UA", utils.ChromeUACH) + req.Header.Set("Sec-CH-UA-Mobile", "?0") + req.Header.Set("Sec-CH-UA-Platform", "Windows") + req.Header.Set("Sec-Fetch-Mode", "navigate") // Vary! + req.Header.Set("Sec-Fetch-Dest", "document") // Vary! + req.Header.Set("Sec-Fetch-Site", "none") + req.Header.Set("Sec-Fetch-User", "?1") 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 3954f8f0d70d..4c5bbddf2521 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -53,6 +53,13 @@ func (c *Config) GetRequestHeader() http.Header { } if header.Get("User-Agent") == "" { header.Set("User-Agent", utils.ChromeUA) + header.Set("Sec-CH-UA", utils.ChromeUACH) + header.Set("Sec-CH-UA-Mobile", "?0") + header.Set("Sec-CH-UA-Platform", "Windows") + header.Set("Sec-Fetch-Mode", "cors") // Vary! + header.Set("Sec-Fetch-Dest", "empty") // Vary! + header.Set("Sec-Fetch-Site", "none") + header.Set("Sec-Fetch-User", "?1") } return header } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 26721bc18e61..6c678520c3a4 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -254,6 +254,13 @@ 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("Sec-CH-UA", utils.ChromeUACH) + req.Header.Set("Sec-CH-UA-Mobile", "?0") + req.Header.Set("Sec-CH-UA-Platform", "Windows") + req.Header.Set("Sec-Fetch-Mode", "cors") // Vary! + req.Header.Set("Sec-Fetch-Dest", "empty") // Vary! + req.Header.Set("Sec-Fetch-Site", "none") + req.Header.Set("Sec-Fetch-User", "?1") req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) resp, err := client.Do(req) From 30a5666d1e2819c864677015bcde65a88aa7c1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 11:05:44 +0000 Subject: [PATCH 06/53] Adjusted headers in HU and WS. --- transport/internet/httpupgrade/dialer.go | 2 +- transport/internet/websocket/config.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index 71f2c5e7ee5e..701658d055e2 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -101,7 +101,7 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * req.Header.Set("Sec-CH-UA", utils.ChromeUACH) req.Header.Set("Sec-CH-UA-Mobile", "?0") req.Header.Set("Sec-CH-UA-Platform", "Windows") - req.Header.Set("Sec-Fetch-Mode", "cors") // Vary! + req.Header.Set("Sec-Fetch-Mode", "websocket") // Vary! req.Header.Set("Sec-Fetch-Dest", "empty") // Vary! req.Header.Set("Sec-Fetch-Site", "none") req.Header.Set("Sec-Fetch-User", "?1") diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index bd38cd4fe837..1628e1e36489 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -26,6 +26,13 @@ func (c *Config) GetRequestHeader() http.Header { } if header.Get("User-Agent") == "" { header.Set("User-Agent", utils.ChromeUA) + header.Set("Sec-CH-UA", utils.ChromeUACH) + header.Set("Sec-CH-UA-Mobile", "?0") + header.Set("Sec-CH-UA-Platform", "Windows") + header.Set("Sec-Fetch-Mode", "websocket") // Vary! + header.Set("Sec-Fetch-Dest", "empty") // Vary! + header.Set("Sec-Fetch-Site", "none") + header.Set("Sec-Fetch-User", "?1") } return header } From 8962725a1f03a82f7891df6cd3cd53fb0f587e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 11:20:51 +0000 Subject: [PATCH 07/53] Oops! --- infra/conf/transport_authenticators.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/infra/conf/transport_authenticators.go b/infra/conf/transport_authenticators.go index f019a4b08fcf..a9590af904b6 100644 --- a/infra/conf/transport_authenticators.go +++ b/infra/conf/transport_authenticators.go @@ -46,31 +46,31 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) { }, { Name: "Sec-CH-UA", - Value: []strings{utils.ChromeUACH}, + Value: []string{utils.ChromeUACH}, }, { Name: "Sec-CH-UA-Mobile", - Value: []strings{"?0"}, + Value: []string{"?0"}, }, { Name: "Sec-CH-UA-Platform", - Value: []strings{"Windows"}, + Value: []string{"Windows"}, }, { Name: "Sec-Fetch-Mode", - Value: []strings{"no-cors", "cors", "same-origin"}, + Value: []string{"no-cors", "cors", "same-origin"}, }, { Name: "Sec-Fetch-Dest", - Value: []strings{"empty"}, + Value: []string{"empty"}, }, { Name: "Sec-Fetch-Site", - Value: []strings{"none"}, + Value: []string{"none"}, }, { Name: "Sec-Fetch-User", - Value: []strings{"?1"}, + Value: []string{"?1"}, }, { Name: "Accept-Encoding", From 0a50325c94a0829a4dfd81eac8dfec432bc27dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:07:26 +0000 Subject: [PATCH 08/53] A shared method for applying headers. --- common/utils/browser.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/common/utils/browser.go b/common/utils/browser.go index 06388f93aec5..9d4ef9cb7650 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -4,6 +4,7 @@ import ( "math/rand" "strconv" "time" + "net/http" "github.com/klauspost/cpuid/v2" ) @@ -35,3 +36,26 @@ func getValidManglingChar() string { // It would be better to have the three parts ordered randomly upon generation var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" + +func ApplyDefaultHeaders(&headers http.Header, browser string, context string) { + switch browser { + case "chrome": + &header.Set("User-Agent", utils.ChromeUA) + &header.Set("Sec-CH-UA", utils.ChromeUACH) + &header.Set("Sec-CH-UA-Mobile", "?0") + &header.Set("Sec-CH-UA-Platform", "Windows") + switch context { + case "nav": + &header.Set("Sec-Fetch-Mode", "navigate") // Vary! + &header.Set("Sec-Fetch-Dest", "document") // Vary! + case "ws": + &header.Set("Sec-Fetch-Mode", "websocket") // Vary! + &header.Set("Sec-Fetch-Dest", "empty") // Vary! + case "fetch": + &header.Set("Sec-Fetch-Mode", "cors") // Vary! + &header.Set("Sec-Fetch-Dest", "empty") // Vary! + } + &header.Set("Sec-Fetch-Site", "none") + &header.Set("Sec-Fetch-User", "?1") + } +} From 79cfded6db0a3f7cfb789cae040f7c12c40cf23c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:10:56 +0000 Subject: [PATCH 09/53] Re-ordered header writing. --- common/utils/browser.go | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 9d4ef9cb7650..167df71492af 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -37,25 +37,27 @@ func getValidManglingChar() string { // It would be better to have the three parts ordered randomly upon generation var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" -func ApplyDefaultHeaders(&headers http.Header, browser string, context string) { +func ApplyDefaultHeaders(headers &http.Header, browser string, context string) { switch browser { - case "chrome": - &header.Set("User-Agent", utils.ChromeUA) - &header.Set("Sec-CH-UA", utils.ChromeUACH) - &header.Set("Sec-CH-UA-Mobile", "?0") - &header.Set("Sec-CH-UA-Platform", "Windows") - switch context { - case "nav": - &header.Set("Sec-Fetch-Mode", "navigate") // Vary! - &header.Set("Sec-Fetch-Dest", "document") // Vary! - case "ws": - &header.Set("Sec-Fetch-Mode", "websocket") // Vary! - &header.Set("Sec-Fetch-Dest", "empty") // Vary! - case "fetch": - &header.Set("Sec-Fetch-Mode", "cors") // Vary! - &header.Set("Sec-Fetch-Dest", "empty") // Vary! - } - &header.Set("Sec-Fetch-Site", "none") - &header.Set("Sec-Fetch-User", "?1") + case "chrome": + &header.Set("User-Agent", utils.ChromeUA) + &header.Set("Sec-CH-UA", utils.ChromeUACH) + &header.Set("Sec-CH-UA-Mobile", "?0") + &header.Set("Sec-CH-UA-Platform", "Windows") } + switch context { + case "nav": + &header.Set("Sec-Fetch-Mode", "navigate") + &header.Set("Sec-Fetch-Dest", "document") + &header.Set("Sec-Fetch-Site", "none") + case "ws": + &header.Set("Sec-Fetch-Mode", "websocket") + &header.Set("Sec-Fetch-Dest", "empty") + &header.Set("Sec-Fetch-Site", "cross-site") + case "fetch": + &header.Set("Sec-Fetch-Mode", "cors") + &header.Set("Sec-Fetch-Dest", "empty") + &header.Set("Sec-Fetch-Site", "cross-site") + } + &header.Set("Sec-Fetch-User", "?1") } From c5e6f51b8ed9ec31a7ba5114767824c6b18b703b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:13:01 +0000 Subject: [PATCH 10/53] Yep I suck at Go... --- common/utils/browser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 167df71492af..d14eda086ed9 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -37,7 +37,7 @@ func getValidManglingChar() string { // It would be better to have the three parts ordered randomly upon generation var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" -func ApplyDefaultHeaders(headers &http.Header, browser string, context string) { +func ApplyDefaultHeaders(headers *http.Header, browser string, context string) { switch browser { case "chrome": &header.Set("User-Agent", utils.ChromeUA) From a42fec711712c3fd14b4f60595636b8fa391c568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:14:36 +0000 Subject: [PATCH 11/53] Note to self: Configure Go 1.26 next time. --- common/utils/browser.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index d14eda086ed9..2bc0cc6c4d0b 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -37,11 +37,11 @@ func getValidManglingChar() string { // It would be better to have the three parts ordered randomly upon generation var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" -func ApplyDefaultHeaders(headers *http.Header, browser string, context string) { +func ApplyDefaultHeaders(header *http.Header, browser string, context string) { switch browser { case "chrome": - &header.Set("User-Agent", utils.ChromeUA) - &header.Set("Sec-CH-UA", utils.ChromeUACH) + &header.Set("User-Agent", ChromeUA) + &header.Set("Sec-CH-UA", ChromeUACH) &header.Set("Sec-CH-UA-Mobile", "?0") &header.Set("Sec-CH-UA-Platform", "Windows") } From a99942e294214c192cd1e9e91a6c9290489f3b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:18:02 +0000 Subject: [PATCH 12/53] Ugh! --- common/utils/browser.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 2bc0cc6c4d0b..89e0dd5914c9 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -37,27 +37,27 @@ func getValidManglingChar() string { // It would be better to have the three parts ordered randomly upon generation var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" -func ApplyDefaultHeaders(header *http.Header, browser string, context string) { +func ApplyDefaultHeaders(header http.Header, browser string, context string) { switch browser { case "chrome": - &header.Set("User-Agent", ChromeUA) - &header.Set("Sec-CH-UA", ChromeUACH) - &header.Set("Sec-CH-UA-Mobile", "?0") - &header.Set("Sec-CH-UA-Platform", "Windows") + header.Set("User-Agent", ChromeUA) + header.Set("Sec-CH-UA", ChromeUACH) + header.Set("Sec-CH-UA-Mobile", "?0") + header.Set("Sec-CH-UA-Platform", "Windows") } switch context { case "nav": - &header.Set("Sec-Fetch-Mode", "navigate") - &header.Set("Sec-Fetch-Dest", "document") - &header.Set("Sec-Fetch-Site", "none") + header.Set("Sec-Fetch-Mode", "navigate") + header.Set("Sec-Fetch-Dest", "document") + header.Set("Sec-Fetch-Site", "none") case "ws": - &header.Set("Sec-Fetch-Mode", "websocket") - &header.Set("Sec-Fetch-Dest", "empty") - &header.Set("Sec-Fetch-Site", "cross-site") + header.Set("Sec-Fetch-Mode", "websocket") + header.Set("Sec-Fetch-Dest", "empty") + header.Set("Sec-Fetch-Site", "cross-site") case "fetch": - &header.Set("Sec-Fetch-Mode", "cors") - &header.Set("Sec-Fetch-Dest", "empty") - &header.Set("Sec-Fetch-Site", "cross-site") + header.Set("Sec-Fetch-Mode", "cors") + header.Set("Sec-Fetch-Dest", "empty") + header.Set("Sec-Fetch-Site", "cross-site") } - &header.Set("Sec-Fetch-User", "?1") + header.Set("Sec-Fetch-User", "?1") } From f7c68ca943bcae7b94d9b0c2342d6c95b859c392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:20:37 +0000 Subject: [PATCH 13/53] Does it work? --- transport/internet/websocket/config.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 1628e1e36489..191db6a5f3d1 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -25,14 +25,7 @@ func (c *Config) GetRequestHeader() http.Header { header.Add(k, v) } if header.Get("User-Agent") == "" { - header.Set("User-Agent", utils.ChromeUA) - header.Set("Sec-CH-UA", utils.ChromeUACH) - header.Set("Sec-CH-UA-Mobile", "?0") - header.Set("Sec-CH-UA-Platform", "Windows") - header.Set("Sec-Fetch-Mode", "websocket") // Vary! - header.Set("Sec-Fetch-Dest", "empty") // Vary! - header.Set("Sec-Fetch-Site", "none") - header.Set("Sec-Fetch-User", "?1") + utils.ApplyDefaultHeaders(header, "chrome", "ws") } return header } From 02bc287973211c5626701a4f2abe294fa0de6f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:28:56 +0000 Subject: [PATCH 14/53] I'm hopeful. --- app/dns/nameserver_doh.go | 9 +-------- app/observatory/burst/ping.go | 9 +-------- app/observatory/observer.go | 9 +-------- proxy/http/client.go | 9 +-------- transport/internet/httpupgrade/dialer.go | 9 +-------- transport/internet/reality/reality.go | 9 +-------- transport/internet/splithttp/config.go | 9 +-------- transport/internet/tls/ech.go | 9 +-------- 8 files changed, 8 insertions(+), 64 deletions(-) diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go index 6978802bdb9a..275323027870 100644 --- a/app/dns/nameserver_doh.go +++ b/app/dns/nameserver_doh.go @@ -214,14 +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("Sec-CH-UA", utils.ChromeUACH) - req.Header.Set("Sec-CH-UA-Mobile", "?0") - req.Header.Set("Sec-CH-UA-Platform", "Windows") - req.Header.Set("Sec-Fetch-Mode", "cors") // Vary! - req.Header.Set("Sec-Fetch-Dest", "empty") // Vary! - req.Header.Set("Sec-Fetch-Site", "none") - req.Header.Set("Sec-Fetch-User", "?1") + utils.ApplyDefaultHeaders(req.Header, "chrome", "fetch") 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 f03bfd4a3268..e880ec954b76 100644 --- a/app/observatory/burst/ping.go +++ b/app/observatory/burst/ping.go @@ -62,14 +62,7 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) { if err != nil { return rttFailed, err } - req.Header.Set("User-Agent", utils.ChromeUA) - req.Header.Set("Sec-CH-UA", utils.ChromeUACH) - req.Header.Set("Sec-CH-UA-Mobile", "?0") - req.Header.Set("Sec-CH-UA-Platform", "Windows") - req.Header.Set("Sec-Fetch-Mode", "navigate") // Vary! - req.Header.Set("Sec-Fetch-Dest", "document") // Vary! - req.Header.Set("Sec-Fetch-Site", "none") - req.Header.Set("Sec-Fetch-User", "?1") + utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") start := time.Now() resp, err := s.httpClient.Do(req) diff --git a/app/observatory/observer.go b/app/observatory/observer.go index 168a7683311d..8e9014c7adbc 100644 --- a/app/observatory/observer.go +++ b/app/observatory/observer.go @@ -164,14 +164,7 @@ func (o *Observer) probe(outbound string) ProbeResult { probeURL = o.config.ProbeUrl } req, _ := http.NewRequest(http.MethodGet, probeURL, nil) - req.Header.Set("User-Agent", utils.ChromeUA) - req.Header.Set("Sec-CH-UA", utils.ChromeUACH) - req.Header.Set("Sec-CH-UA-Mobile", "?0") - req.Header.Set("Sec-CH-UA-Platform", "Windows") - req.Header.Set("Sec-Fetch-Mode", "navigate") // Vary! - req.Header.Set("Sec-Fetch-Dest", "document") // Vary! - req.Header.Set("Sec-Fetch-Site", "none") - req.Header.Set("Sec-Fetch-User", "?1") + utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") 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 4d3a90046955..30e2105f4e49 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -221,14 +221,7 @@ 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) - req.Header.Set("Sec-CH-UA", utils.ChromeUACH) - req.Header.Set("Sec-CH-UA-Mobile", "?0") - req.Header.Set("Sec-CH-UA-Platform", "Windows") - req.Header.Set("Sec-Fetch-Mode", "cors") // Vary! - req.Header.Set("Sec-Fetch-Dest", "empty") // Vary! - req.Header.Set("Sec-Fetch-Site", "none") - req.Header.Set("Sec-Fetch-User", "?1") + utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") } connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index 701658d055e2..d3eef4e38697 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -97,14 +97,7 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * AddHeader(req.Header, key, value) } if req.Header.Get("User-Agent") == "" { - req.Header.Set("User-Agent", utils.ChromeUA) - req.Header.Set("Sec-CH-UA", utils.ChromeUACH) - req.Header.Set("Sec-CH-UA-Mobile", "?0") - req.Header.Set("Sec-CH-UA-Platform", "Windows") - req.Header.Set("Sec-Fetch-Mode", "websocket") // Vary! - req.Header.Set("Sec-Fetch-Dest", "empty") // Vary! - req.Header.Set("Sec-Fetch-Site", "none") - req.Header.Set("Sec-Fetch-User", "?1") + utils.ApplyDefaultHeaders(req.Header, "chrome", "ws") } 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 0449f41da2bb..a9bddfed2c60 100644 --- a/transport/internet/reality/reality.go +++ b/transport/internet/reality/reality.go @@ -223,14 +223,7 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati if req == nil { return } - req.Header.Set("User-Agent", utils.ChromeUA) - req.Header.Set("Sec-CH-UA", utils.ChromeUACH) - req.Header.Set("Sec-CH-UA-Mobile", "?0") - req.Header.Set("Sec-CH-UA-Platform", "Windows") - req.Header.Set("Sec-Fetch-Mode", "navigate") // Vary! - req.Header.Set("Sec-Fetch-Dest", "document") // Vary! - req.Header.Set("Sec-Fetch-Site", "none") - req.Header.Set("Sec-Fetch-User", "?1") + utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") 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 4c5bbddf2521..aead1a05faed 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -52,14 +52,7 @@ func (c *Config) GetRequestHeader() http.Header { header.Add(k, v) } if header.Get("User-Agent") == "" { - header.Set("User-Agent", utils.ChromeUA) - header.Set("Sec-CH-UA", utils.ChromeUACH) - header.Set("Sec-CH-UA-Mobile", "?0") - header.Set("Sec-CH-UA-Platform", "Windows") - header.Set("Sec-Fetch-Mode", "cors") // Vary! - header.Set("Sec-Fetch-Dest", "empty") // Vary! - header.Set("Sec-Fetch-Site", "none") - header.Set("Sec-Fetch-User", "?1") + utils.ApplyDefaultHeaders(header, "chrome", "fetch") } return header } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 6c678520c3a4..b874209c9f2d 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -253,14 +253,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("Sec-CH-UA", utils.ChromeUACH) - req.Header.Set("Sec-CH-UA-Mobile", "?0") - req.Header.Set("Sec-CH-UA-Platform", "Windows") - req.Header.Set("Sec-Fetch-Mode", "cors") // Vary! - req.Header.Set("Sec-Fetch-Dest", "empty") // Vary! - req.Header.Set("Sec-Fetch-Site", "none") - req.Header.Set("Sec-Fetch-User", "?1") + utils.ApplyDefaultHeaders(req.Header, "chrome", "fetch") req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) resp, err := client.Do(req) From 393ad42972180d005ef642793a6b233b0ca3f631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:54:37 +0000 Subject: [PATCH 15/53] Full headers from both Chrome and Firefox ESR. --- common/utils/browser.go | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/common/utils/browser.go b/common/utils/browser.go index 89e0dd5914c9..f604d4f50c68 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -38,26 +38,68 @@ func getValidManglingChar() string { var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" func ApplyDefaultHeaders(header http.Header, browser string, context string) { + // Browser-specific switch browser { case "chrome": header.Set("User-Agent", ChromeUA) header.Set("Sec-CH-UA", ChromeUACH) header.Set("Sec-CH-UA-Mobile", "?0") header.Set("Sec-CH-UA-Platform", "Windows") + header.Set("Accept-Language", "en-US,en;q=0.9") + case "firefox": + header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0") // Can have a Firefox ESR version generator later + header.Set("Accept-Language", "en-US,en;q=0.5") } + // Context-specific switch context { case "nav": header.Set("Sec-Fetch-Mode", "navigate") header.Set("Sec-Fetch-Dest", "document") header.Set("Sec-Fetch-Site", "none") + header.Set("Upgrade-Insecure-Requests", "1") + header.Set("Priority", "u=0, i") + if header.Get("Cache-Control") == "" { + switch browser { + case "chrome": + header.Set("Cache-Control", "max-age=0") + } + } + if header.Get("Accept") == "" { + switch browser { + case "chrome": + header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7") + case "firefox": + header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + } + } case "ws": header.Set("Sec-Fetch-Mode", "websocket") header.Set("Sec-Fetch-Dest", "empty") header.Set("Sec-Fetch-Site", "cross-site") + if header.Get("Cache-Control") == "" { + header.Set("Cache-Control", "no-cache") + } + if header.Get("Accept") == "" { + header.Set("Accept", "*/*") + } case "fetch": header.Set("Sec-Fetch-Mode", "cors") header.Set("Sec-Fetch-Dest", "empty") header.Set("Sec-Fetch-Site", "cross-site") + if header.Get("Priority") == "" { + switch browser { + case "chrome": + header.Set("Priority", "u=1, i") + case "firefox": + header.Set("Priority", "u=4") + } + } + if header.Get("Cache-Control") == "" { + header.Set("Cache-Control", "no-cache") + } + if header.Get("Accept") == "" { + header.Set("Accept", "*/*") + } } header.Set("Sec-Fetch-User", "?1") } From 23fc8832454aadd843b18b50e64d4c0c14177d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 13:05:45 +0000 Subject: [PATCH 16/53] Avoided argument naming confusion. --- common/utils/browser.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index f604d4f50c68..44ad08a8dc6f 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -37,7 +37,7 @@ func getValidManglingChar() string { // It would be better to have the three parts ordered randomly upon generation var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" -func ApplyDefaultHeaders(header http.Header, browser string, context string) { +func ApplyDefaultHeaders(header http.Header, browser string, variant string) { // Browser-specific switch browser { case "chrome": @@ -50,8 +50,8 @@ func ApplyDefaultHeaders(header http.Header, browser string, context string) { header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0") // Can have a Firefox ESR version generator later header.Set("Accept-Language", "en-US,en;q=0.5") } - // Context-specific - switch context { + // variant-specific + switch variant { case "nav": header.Set("Sec-Fetch-Mode", "navigate") header.Set("Sec-Fetch-Dest", "document") From 0d79813dacdbb2500b256f16e3fbfce8617deab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 13:28:34 +0000 Subject: [PATCH 17/53] Implemented Chromium's brand GREASE algorithm. --- common/utils/browser.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 44ad08a8dc6f..7cb11c43c943 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -25,17 +25,17 @@ func ChromeVersion() int { return version - 1 } +var clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"} +var clientHintVersionNA = []string{"8", "99", "24"} +func getGreasedChInvalidBrand(seed int) string { + return "\"Not" + clientHintGreaseNA[seed % len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed + 1) % len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed % len(clientHintVersionNA)] + "\""; +} + // ChromeUA provides default browser User-Agent based on CPU-seeded PRNG. var AnchoredChromeVersion = ChromeVersion() var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36" - -func getValidManglingChar() string { - // Valid characters for the mangled Sec-CH-UA header - return string(" ,-_:;()"[rand.Int() & 7]); -} - // It would be better to have the three parts ordered randomly upon generation -var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Not" + getValidManglingChar() + "A" + getValidManglingChar() + "Brand\";v=\"9" + string("6789"[rand.Int() & 3]) + "\"" +var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", " + getGreasedChInvalidBrand(AnchoredChromeVersion) func ApplyDefaultHeaders(header http.Header, browser string, variant string) { // Browser-specific From 2d45dd5fda0b8962b5571b6e952554de9577365e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:12:50 +0000 Subject: [PATCH 18/53] Full Chromium brand GREASE implementation. --- common/utils/browser.go | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 7cb11c43c943..4ed0ff81d6c3 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -25,17 +25,51 @@ func ChromeVersion() int { return version - 1 } +// The full Chromium brand GREASE implementation var clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"} var clientHintVersionNA = []string{"8", "99", "24"} +var clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}} +var clientHintShuffle4 = [][4]int{ + {0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1}, + {1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0}, + {2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, + {3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}} func getGreasedChInvalidBrand(seed int) string { return "\"Not" + clientHintGreaseNA[seed % len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed + 1) % len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed % len(clientHintVersionNA)] + "\""; } +func getGreasedChOrder(brandLength int, seed int) []int { + switch brandLength { + case 1: + return []int{0} + case 2: + return []int{seed % brandLength, (seed + 1) % brandLength} + case 3: + return clientHintShuffle3[seed % len(clientHintShuffle3)][:] + default: + return clientHintShuffle4[seed % len(clientHintShuffle4)][:] + } + return []int{} +} +func getUngreasedChUa(majorVersion int) []string { + return []string {getGreasedChInvalidBrand(majorVersion), + "\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\"", + "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\""} +} +func getGreasedChUa(majorVersion int) string { + rawCh := getUngreasedChUa(majorVersion) + shuffleMap := getGreasedChOrder(len(rawCh), majorVersion) + shuffledCh := make([]string, len(rawCh)) + for i, e := range shuffleMap { + shuffledCh[e] = rawCh[i] + } + return strings.Join(shuffledCh, ", ") +} // ChromeUA provides default browser User-Agent based on CPU-seeded PRNG. var AnchoredChromeVersion = ChromeVersion() var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36" // It would be better to have the three parts ordered randomly upon generation -var ChromeUACH = "\"Google Chrome\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", \"Chromium\";v=\"" + strconv.Itoa(AnchoredChromeVersion) + "\", " + getGreasedChInvalidBrand(AnchoredChromeVersion) +var ChromeUACH = getGreasedChUa(AnchoredChromeVersion) func ApplyDefaultHeaders(header http.Header, browser string, variant string) { // Browser-specific From fcc7feb9bd13ee3d4c9216e4e64d38efd3ad8581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:14:09 +0000 Subject: [PATCH 19/53] Always remember to include imports! --- common/utils/browser.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common/utils/browser.go b/common/utils/browser.go index 4ed0ff81d6c3..30cef67cefe3 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -5,6 +5,7 @@ import ( "strconv" "time" "net/http" + "strings" "github.com/klauspost/cpuid/v2" ) From d9e512b7a70888a087929f2b692ac509a2e2ce56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:46:12 +0000 Subject: [PATCH 20/53] Why weren't some headers added? --- common/utils/browser.go | 4 ++++ transport/internet/websocket/config.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/common/utils/browser.go b/common/utils/browser.go index 30cef67cefe3..de06a21ee65e 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -6,6 +6,7 @@ import ( "time" "net/http" "strings" + "fmt" "github.com/klauspost/cpuid/v2" ) @@ -137,4 +138,7 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { } } header.Set("Sec-Fetch-User", "?1") + for key, value := range header { + fmt.Println("Added header - " + key + ": " + value) + } } diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 191db6a5f3d1..23561764458b 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -27,6 +27,9 @@ func (c *Config) GetRequestHeader() http.Header { if header.Get("User-Agent") == "" { utils.ApplyDefaultHeaders(header, "chrome", "ws") } + for key, value := range header { + fmt.Println("Has header - " + key + ": " + value) + } return header } From 8b6961b806c7dc80190fb93da877a9a8ee287941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:47:48 +0000 Subject: [PATCH 21/53] Bollocks. --- transport/internet/websocket/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 23561764458b..c5c57fc0fd0b 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -2,6 +2,7 @@ package websocket import ( "net/http" + "fmt" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/utils" From 15da2fc65a784c623e4dd910e8d03f3943f2d6b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:51:53 +0000 Subject: [PATCH 22/53] Bloody hell! --- common/utils/browser.go | 2 +- transport/internet/websocket/config.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index de06a21ee65e..ef7b33d6ec3c 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -139,6 +139,6 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { } header.Set("Sec-Fetch-User", "?1") for key, value := range header { - fmt.Println("Added header - " + key + ": " + value) + fmt.Println("Added header - " + key + ": ", value) } } diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index c5c57fc0fd0b..8271c897d4fa 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -29,7 +29,7 @@ func (c *Config) GetRequestHeader() http.Header { utils.ApplyDefaultHeaders(header, "chrome", "ws") } for key, value := range header { - fmt.Println("Has header - " + key + ": " + value) + fmt.Println("Has header - " + key + ":", value) } return header } From 53c980d268a5525823e209cbbc20b42251a765a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:03:30 +0000 Subject: [PATCH 23/53] So the headers already were initiated before?! --- transport/internet/websocket/config.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 8271c897d4fa..0448e973559a 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -25,6 +25,9 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Header { header.Add(k, v) } + for key, value := range header { + fmt.Println("Had header - " + key + ":", value) + } if header.Get("User-Agent") == "" { utils.ApplyDefaultHeaders(header, "chrome", "ws") } From 010879355dbed31864bb2864441283d0a1417345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:17:59 +0000 Subject: [PATCH 24/53] What if I disable UA write in gRPC? --- transport/internet/grpc/dial.go | 4 ++-- transport/internet/websocket/config.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index b8f77c0ea8cc..2b7bdbb77876 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -191,9 +191,9 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in ) if err == nil { userAgent := grpcSettings.UserAgent - if userAgent == "" { + /*if userAgent == "" { userAgent = utils.ChromeUA - } + }*/ setUserAgent(conn, userAgent) conn.Connect() } diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 0448e973559a..70b2f570b7e9 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -28,6 +28,7 @@ func (c *Config) GetRequestHeader() http.Header { for key, value := range header { fmt.Println("Had header - " + key + ":", value) } + // UA header would have already been set by now with the current implementation if header.Get("User-Agent") == "" { utils.ApplyDefaultHeaders(header, "chrome", "ws") } From 009237a6a79479909cf1cfec5d4283822e50e6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:20:32 +0000 Subject: [PATCH 25/53] For Sunbutt's sake, Go! --- transport/internet/grpc/dial.go | 1 - 1 file changed, 1 deletion(-) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 2b7bdbb77876..b6f0402f7d86 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -11,7 +11,6 @@ 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" From 14ea9b1b062a82b78387915c75aff55d0f42203c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:28:17 +0000 Subject: [PATCH 26/53] As long as it works. But still... Why wasn't the user agent string blank when the WS connections are being initiated? --- common/utils/browser.go | 4 ++-- transport/internet/grpc/dial.go | 5 +++-- transport/internet/websocket/config.go | 10 +++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index ef7b33d6ec3c..19f787ea67af 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -138,7 +138,7 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { } } header.Set("Sec-Fetch-User", "?1") - for key, value := range header { + /*for key, value := range header { fmt.Println("Added header - " + key + ": ", value) - } + }*/ } diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index b6f0402f7d86..b8f77c0ea8cc 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -11,6 +11,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" @@ -190,9 +191,9 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in ) if err == nil { userAgent := grpcSettings.UserAgent - /*if userAgent == "" { + if userAgent == "" { userAgent = utils.ChromeUA - }*/ + } setUserAgent(conn, userAgent) conn.Connect() } diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 70b2f570b7e9..263c74a79738 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -25,16 +25,16 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Header { header.Add(k, v) } - for key, value := range header { + /*for key, value := range header { fmt.Println("Had header - " + key + ":", value) - } + }*/ // UA header would have already been set by now with the current implementation - if header.Get("User-Agent") == "" { + if header.Get("User-Agent") == "" || header.Get("Sec-Fetch-Mode") == "" { utils.ApplyDefaultHeaders(header, "chrome", "ws") } - for key, value := range header { + /*for key, value := range header { fmt.Println("Has header - " + key + ":", value) - } + }*/ return header } From 14caef5d6ce64f24b7aec580ebaaa0af3e4535c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:31:20 +0000 Subject: [PATCH 27/53] Fuck you Go! --- common/utils/browser.go | 1 - transport/internet/websocket/config.go | 1 - 2 files changed, 2 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 19f787ea67af..2a518d669b1c 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -6,7 +6,6 @@ import ( "time" "net/http" "strings" - "fmt" "github.com/klauspost/cpuid/v2" ) diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 263c74a79738..452be7633abe 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -2,7 +2,6 @@ package websocket import ( "net/http" - "fmt" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/utils" From 22f90073c634b6da22b5d411e2d29d899ceff2ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:37:17 +0000 Subject: [PATCH 28/53] Final test. --- transport/internet/websocket/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 452be7633abe..09c79596ddbc 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -28,7 +28,7 @@ func (c *Config) GetRequestHeader() http.Header { fmt.Println("Had header - " + key + ":", value) }*/ // UA header would have already been set by now with the current implementation - if header.Get("User-Agent") == "" || header.Get("Sec-Fetch-Mode") == "" { + if header.Get("User-Agent") == "" { utils.ApplyDefaultHeaders(header, "chrome", "ws") } /*for key, value := range header { From b00a07e464844e79ddbc09b10cdc19e73114c5c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sun, 15 Mar 2026 10:55:03 +0000 Subject: [PATCH 29/53] Near-perfect match on final check on headers. --- common/utils/browser.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 2a518d669b1c..8b405a5702fe 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -91,6 +91,7 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { header.Set("Sec-Fetch-Mode", "navigate") header.Set("Sec-Fetch-Dest", "document") header.Set("Sec-Fetch-Site", "none") + header.Set("Sec-Fetch-User", "?1") header.Set("Upgrade-Insecure-Requests", "1") header.Set("Priority", "u=0, i") if header.Get("Cache-Control") == "" { @@ -114,6 +115,9 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { if header.Get("Cache-Control") == "" { header.Set("Cache-Control", "no-cache") } + if header.Get("Pragma") == "" { + header.Set("Pragma", "no-cache") + } if header.Get("Accept") == "" { header.Set("Accept", "*/*") } @@ -132,11 +136,13 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { if header.Get("Cache-Control") == "" { header.Set("Cache-Control", "no-cache") } + if header.Get("Pragma") == "" { + header.Set("Pragma", "no-cache") + } if header.Get("Accept") == "" { header.Set("Accept", "*/*") } } - header.Set("Sec-Fetch-User", "?1") /*for key, value := range header { fmt.Println("Added header - " + key + ": ", value) }*/ From b6fe4d8d56ef38c12028eb956cc96464aba9d431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:03:48 +0000 Subject: [PATCH 30/53] Fuuuuuuuuuuuuuuuuuuuuuck --- common/utils/browser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 8b405a5702fe..9a4610ea6858 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -79,7 +79,7 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { header.Set("User-Agent", ChromeUA) header.Set("Sec-CH-UA", ChromeUACH) header.Set("Sec-CH-UA-Mobile", "?0") - header.Set("Sec-CH-UA-Platform", "Windows") + header.Set("Sec-CH-UA-Platform", "\"Windows\"") header.Set("Accept-Language", "en-US,en;q=0.9") case "firefox": header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0") // Can have a Firefox ESR version generator later From 01377d36441942704b1fdf03700fb28a7ed7cf6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Mon, 16 Mar 2026 05:50:56 +0000 Subject: [PATCH 31/53] `same-origin`. --- 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 9a4610ea6858..7c57c646fce1 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -111,7 +111,7 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { case "ws": header.Set("Sec-Fetch-Mode", "websocket") header.Set("Sec-Fetch-Dest", "empty") - header.Set("Sec-Fetch-Site", "cross-site") + header.Set("Sec-Fetch-Site", "same-origin") if header.Get("Cache-Control") == "" { header.Set("Cache-Control", "no-cache") } @@ -124,7 +124,7 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { case "fetch": header.Set("Sec-Fetch-Mode", "cors") header.Set("Sec-Fetch-Dest", "empty") - header.Set("Sec-Fetch-Site", "cross-site") + header.Set("Sec-Fetch-Site", "same-origin") if header.Get("Priority") == "" { switch browser { case "chrome": From afed94c58ab852d5e6998c26ac241903c89c441f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:11:42 +0000 Subject: [PATCH 32/53] Browsers are fundamentally incapable of true gRPC. --- transport/internet/grpc/dial.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index b8f77c0ea8cc..2e2bf84f4402 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -191,9 +191,6 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in ) if err == nil { userAgent := grpcSettings.UserAgent - if userAgent == "" { - userAgent = utils.ChromeUA - } setUserAgent(conn, userAgent) conn.Connect() } From 7c084626deef8b4c116f295f137e2578678fc202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:18:09 +0000 Subject: [PATCH 33/53] A better way to test the presence of UA. --- transport/internet/websocket/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 09c79596ddbc..a6fc262bb12a 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -28,7 +28,7 @@ func (c *Config) GetRequestHeader() http.Header { fmt.Println("Had header - " + key + ":", value) }*/ // UA header would have already been set by now with the current implementation - if header.Get("User-Agent") == "" { + if len(header.Values("User-Agent")) < 1 { utils.ApplyDefaultHeaders(header, "chrome", "ws") } /*for key, value := range header { From 4d3a412a9bcd64e17c253e0787c91277cd5499d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:20:16 +0000 Subject: [PATCH 34/53] Are you fucking kidding me? --- transport/internet/grpc/dial.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 2e2bf84f4402..741373f002bf 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -11,7 +11,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/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" From f749b66a916729cae353e6369d2805cc23446391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:43:02 +0000 Subject: [PATCH 35/53] Cleanup & commented out lines for behaviour switch. --- proxy/http/client.go | 1 + transport/internet/httpupgrade/dialer.go | 1 + transport/internet/splithttp/config.go | 1 + transport/internet/websocket/config.go | 9 ++------- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/proxy/http/client.go b/proxy/http/client.go index 30e2105f4e49..b4dd945db95d 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -220,6 +220,7 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u for _, h := range header { req.Header.Set(h.Key, h.Value) } + //if len(header.Values("User-Agent")) < 1 { if req.Header.Get("User-Agent") == "" { utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") } diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index d3eef4e38697..c41373f94607 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -96,6 +96,7 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * for key, value := range transportConfiguration.Header { AddHeader(req.Header, key, value) } + //if len(header.Values("User-Agent")) < 1 { if req.Header.Get("User-Agent") == "" { utils.ApplyDefaultHeaders(req.Header, "chrome", "ws") } diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index aead1a05faed..27ab8d4cb569 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -51,6 +51,7 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Headers { header.Add(k, v) } + //if len(header.Values("User-Agent")) < 1 { if header.Get("User-Agent") == "" { utils.ApplyDefaultHeaders(header, "chrome", "fetch") } diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index a6fc262bb12a..2160b600b2ca 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -24,16 +24,11 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Header { header.Add(k, v) } - /*for key, value := range header { - fmt.Println("Had header - " + key + ":", value) - }*/ // UA header would have already been set by now with the current implementation - if len(header.Values("User-Agent")) < 1 { + //if len(header.Values("User-Agent")) < 1 { + if header.Get("User-Agent") == "" { utils.ApplyDefaultHeaders(header, "chrome", "ws") } - /*for key, value := range header { - fmt.Println("Has header - " + key + ":", value) - }*/ return header } From cdd893124774e1990fd340e3e1c1264ad476a750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:53:49 +0000 Subject: [PATCH 36/53] Bypass canonical headers for `CH-UA` headers. --- common/utils/browser.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 7c57c646fce1..eff5900e8d49 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -76,30 +76,27 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { // Browser-specific switch browser { case "chrome": + header["Sec-CH-UA"] = []string{ChromeUACH} + header["Sec-CH-UA-Mobile"] = []string{"?0"} + header["Sec-CH-UA-Platform"] = []string{"\"Windows\""} + header["DNT"] = []string{"1"} header.Set("User-Agent", ChromeUA) - header.Set("Sec-CH-UA", ChromeUACH) - header.Set("Sec-CH-UA-Mobile", "?0") - header.Set("Sec-CH-UA-Platform", "\"Windows\"") header.Set("Accept-Language", "en-US,en;q=0.9") case "firefox": header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0") // Can have a Firefox ESR version generator later + header["DNT"] = []string{"1"} header.Set("Accept-Language", "en-US,en;q=0.5") } // variant-specific switch variant { case "nav": - header.Set("Sec-Fetch-Mode", "navigate") - header.Set("Sec-Fetch-Dest", "document") - header.Set("Sec-Fetch-Site", "none") - header.Set("Sec-Fetch-User", "?1") - header.Set("Upgrade-Insecure-Requests", "1") - header.Set("Priority", "u=0, i") if header.Get("Cache-Control") == "" { switch browser { case "chrome": header.Set("Cache-Control", "max-age=0") } } + header.Set("Upgrade-Insecure-Requests", "1") if header.Get("Accept") == "" { switch browser { case "chrome": @@ -108,6 +105,11 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") } } + header.Set("Sec-Fetch-Site", "none") + header.Set("Sec-Fetch-Mode", "navigate") + header.Set("Sec-Fetch-User", "?1") + header.Set("Sec-Fetch-Dest", "document") + header.Set("Priority", "u=0, i") case "ws": header.Set("Sec-Fetch-Mode", "websocket") header.Set("Sec-Fetch-Dest", "empty") From b4d2b2a5deae8a5c4f5a8eac1316c4d679bdbb81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:00:09 +0000 Subject: [PATCH 37/53] `!chrome`, `!firefox` --- proxy/http/client.go | 5 ++++- transport/internet/grpc/dial.go | 1 - transport/internet/httpupgrade/dialer.go | 5 ++++- transport/internet/splithttp/config.go | 5 ++++- transport/internet/websocket/config.go | 5 ++++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/proxy/http/client.go b/proxy/http/client.go index b4dd945db95d..2b16bf09d08e 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -221,8 +221,11 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u req.Header.Set(h.Key, h.Value) } //if len(header.Values("User-Agent")) < 1 { - if req.Header.Get("User-Agent") == "" { + switch req.Header.Get("User-Agent") { + case "", "!chrome": utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") + case "!firefox": + utils.ApplyDefaultHeaders(req.Header, "firefox", "nav") } connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 741373f002bf..97a8750edc1d 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -11,7 +11,6 @@ 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" diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index c41373f94607..aedd73c0ae2d 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -97,8 +97,11 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * AddHeader(req.Header, key, value) } //if len(header.Values("User-Agent")) < 1 { - if req.Header.Get("User-Agent") == "" { + switch req.Header.Get("User-Agent") { + case "", "!chrome": utils.ApplyDefaultHeaders(req.Header, "chrome", "ws") + case "!firefox": + utils.ApplyDefaultHeaders(req.Header, "firefox", "ws") } req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "websocket") diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index 27ab8d4cb569..3612d40c0a4c 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -52,8 +52,11 @@ func (c *Config) GetRequestHeader() http.Header { header.Add(k, v) } //if len(header.Values("User-Agent")) < 1 { - if header.Get("User-Agent") == "" { + switch req.Header.Get("User-Agent") { + case "", "!chrome": utils.ApplyDefaultHeaders(header, "chrome", "fetch") + case "!firefox": + utils.ApplyDefaultHeaders(header, "firefox", "fetch") } return header } diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 2160b600b2ca..3cb38c7d0b76 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -26,8 +26,11 @@ func (c *Config) GetRequestHeader() http.Header { } // UA header would have already been set by now with the current implementation //if len(header.Values("User-Agent")) < 1 { - if header.Get("User-Agent") == "" { + switch req.Header.Get("User-Agent") { + case "", "!chrome": utils.ApplyDefaultHeaders(header, "chrome", "ws") + case "!firefox": + utils.ApplyDefaultHeaders(header, "firefox", "ws") } return header } From ee9589af81b4121b9c7ed47d24e5e3efcd0abcfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:04:39 +0000 Subject: [PATCH 38/53] May Sunbutt and Moonbutt pass this commit. --- common/utils/browser.go | 7 ++++--- proxy/http/client.go | 2 ++ transport/internet/httpupgrade/dialer.go | 2 ++ transport/internet/splithttp/config.go | 2 ++ transport/internet/websocket/config.go | 2 ++ 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index eff5900e8d49..f984b0711a11 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -86,6 +86,10 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0") // Can have a Firefox ESR version generator later header["DNT"] = []string{"1"} header.Set("Accept-Language", "en-US,en;q=0.5") + case "go": + // Expose the default net/http header + header.Del("User-Agent") + return } // variant-specific switch variant { @@ -145,7 +149,4 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { header.Set("Accept", "*/*") } } - /*for key, value := range header { - fmt.Println("Added header - " + key + ": ", value) - }*/ } diff --git a/proxy/http/client.go b/proxy/http/client.go index 2b16bf09d08e..c1d8207e217e 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -226,6 +226,8 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") case "!firefox": utils.ApplyDefaultHeaders(req.Header, "firefox", "nav") + case "!go": + utils.ApplyDefaultHeaders(req.Header, "go", "nav") } connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index aedd73c0ae2d..e798db5bc31b 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -102,6 +102,8 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * utils.ApplyDefaultHeaders(req.Header, "chrome", "ws") case "!firefox": utils.ApplyDefaultHeaders(req.Header, "firefox", "ws") + case "!go": + utils.ApplyDefaultHeaders(req.Header, "go", "ws") } req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "websocket") diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index 3612d40c0a4c..eb7d07348efb 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -57,6 +57,8 @@ func (c *Config) GetRequestHeader() http.Header { utils.ApplyDefaultHeaders(header, "chrome", "fetch") case "!firefox": utils.ApplyDefaultHeaders(header, "firefox", "fetch") + case "!go": + utils.ApplyDefaultHeaders(req.Header, "go", "fetch") } return header } diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 3cb38c7d0b76..079608d53a85 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -31,6 +31,8 @@ func (c *Config) GetRequestHeader() http.Header { utils.ApplyDefaultHeaders(header, "chrome", "ws") case "!firefox": utils.ApplyDefaultHeaders(header, "firefox", "ws") + case "!go": + utils.ApplyDefaultHeaders(req.Header, "go", "ws") } return header } From 6b47fa9c762995eb85a64c3a94f6b9ba35e70459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:09:38 +0000 Subject: [PATCH 39/53] Celestia has a giant cake-infused rear! --- transport/internet/splithttp/config.go | 4 ++-- transport/internet/websocket/config.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index eb7d07348efb..313fb3902bf7 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -52,13 +52,13 @@ func (c *Config) GetRequestHeader() http.Header { header.Add(k, v) } //if len(header.Values("User-Agent")) < 1 { - switch req.Header.Get("User-Agent") { + switch header.Get("User-Agent") { case "", "!chrome": utils.ApplyDefaultHeaders(header, "chrome", "fetch") case "!firefox": utils.ApplyDefaultHeaders(header, "firefox", "fetch") case "!go": - utils.ApplyDefaultHeaders(req.Header, "go", "fetch") + utils.ApplyDefaultHeaders(header, "go", "fetch") } return header } diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 079608d53a85..bcab1e85f7e1 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -26,13 +26,13 @@ func (c *Config) GetRequestHeader() http.Header { } // UA header would have already been set by now with the current implementation //if len(header.Values("User-Agent")) < 1 { - switch req.Header.Get("User-Agent") { + switch header.Get("User-Agent") { case "", "!chrome": utils.ApplyDefaultHeaders(header, "chrome", "ws") case "!firefox": utils.ApplyDefaultHeaders(header, "firefox", "ws") case "!go": - utils.ApplyDefaultHeaders(req.Header, "go", "ws") + utils.ApplyDefaultHeaders(header, "go", "ws") } return header } From d37d16b8842629cd3cd0218d01e17207f689d749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Thu, 19 Mar 2026 14:41:39 +0000 Subject: [PATCH 40/53] Test the waters. --- common/utils/browser.go | 15 +++++++++++++++ transport/internet/websocket/config.go | 11 +---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index f984b0711a11..1b4bf4c34742 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -150,3 +150,18 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { } } } + +func UseDefaultHeadersWith(header http.Header, variant string) { + if len(header.Values("User-Agent")) < 1 { + ApplyDefaultHeaders(header, "chrome", variant) + } else { + switch header.Get("User-Agent") { + case "!chrome": + ApplyDefaultHeaders(header, "chrome", variant) + case "!firefox": + ApplyDefaultHeaders(header, "firefox", variant) + case "!go": + ApplyDefaultHeaders(header, "go", variant) + } + } +} diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index bcab1e85f7e1..b192378e3602 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -24,16 +24,7 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Header { header.Add(k, v) } - // UA header would have already been set by now with the current implementation - //if len(header.Values("User-Agent")) < 1 { - switch header.Get("User-Agent") { - case "", "!chrome": - utils.ApplyDefaultHeaders(header, "chrome", "ws") - case "!firefox": - utils.ApplyDefaultHeaders(header, "firefox", "ws") - case "!go": - utils.ApplyDefaultHeaders(header, "go", "ws") - } + utils.UseDefaultHeadersWith(header, "ws") return header } From d3ff8e7e3b073ff8b9c12f3bb5184bb73df46af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:01:55 +0000 Subject: [PATCH 41/53] Special value handlers to a single common method. --- common/utils/browser.go | 1 + proxy/http/client.go | 10 +--------- transport/internet/httpupgrade/dialer.go | 10 +--------- transport/internet/splithttp/config.go | 10 +--------- 4 files changed, 4 insertions(+), 27 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 1b4bf4c34742..b39903bcb345 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -152,6 +152,7 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { } func UseDefaultHeadersWith(header http.Header, variant string) { + // The global UA special value handler if len(header.Values("User-Agent")) < 1 { ApplyDefaultHeaders(header, "chrome", variant) } else { diff --git a/proxy/http/client.go b/proxy/http/client.go index c1d8207e217e..3fd845aaa04c 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -220,15 +220,7 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u for _, h := range header { req.Header.Set(h.Key, h.Value) } - //if len(header.Values("User-Agent")) < 1 { - switch req.Header.Get("User-Agent") { - case "", "!chrome": - utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") - case "!firefox": - utils.ApplyDefaultHeaders(req.Header, "firefox", "nav") - case "!go": - utils.ApplyDefaultHeaders(req.Header, "go", "nav") - } + utils.UseDefaultHeadersWith(req.Header, "nav") connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { req.Header.Set("Proxy-Connection", "Keep-Alive") diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index e798db5bc31b..59fe61407669 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -96,15 +96,7 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * for key, value := range transportConfiguration.Header { AddHeader(req.Header, key, value) } - //if len(header.Values("User-Agent")) < 1 { - switch req.Header.Get("User-Agent") { - case "", "!chrome": - utils.ApplyDefaultHeaders(req.Header, "chrome", "ws") - case "!firefox": - utils.ApplyDefaultHeaders(req.Header, "firefox", "ws") - case "!go": - utils.ApplyDefaultHeaders(req.Header, "go", "ws") - } + utils.UseDefaultHeadersWith(req.Header, "ws") req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "websocket") diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index 313fb3902bf7..c8f812428e8b 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -51,15 +51,7 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Headers { header.Add(k, v) } - //if len(header.Values("User-Agent")) < 1 { - switch header.Get("User-Agent") { - case "", "!chrome": - utils.ApplyDefaultHeaders(header, "chrome", "fetch") - case "!firefox": - utils.ApplyDefaultHeaders(header, "firefox", "fetch") - case "!go": - utils.ApplyDefaultHeaders(header, "go", "fetch") - } + utils.UseDefaultHeadersWith(header, "fetch") return header } From ff4909e4f7c88f6552e827d22fef109349522efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Fri, 20 Mar 2026 07:56:13 +0000 Subject: [PATCH 42/53] Added headers for Microsoft Edge and `!edge` UA. --- common/utils/browser.go | 49 ++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index b39903bcb345..ad3228f6a82a 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -51,26 +51,36 @@ func getGreasedChOrder(brandLength int, seed int) []int { } return []int{} } -func getUngreasedChUa(majorVersion int) []string { - return []string {getGreasedChInvalidBrand(majorVersion), - "\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\"", - "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\""} +func getUngreasedChUa(majorVersion int, forkName string) []string { + baseChUa := []string{getGreasedChInvalidBrand(majorVersion), + "\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\""} + switch forkName { + case "chrome": + append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"") + case "edge": + append(baseChUa, "\"Microsoft Edge\";v=\"" + strconv.Itoa(majorVersion) + "\"") + } + return baseChUa } -func getGreasedChUa(majorVersion int) string { - rawCh := getUngreasedChUa(majorVersion) - shuffleMap := getGreasedChOrder(len(rawCh), majorVersion) - shuffledCh := make([]string, len(rawCh)) +func getGreasedChUa(majorVersion int, forkName string) string { + ungreasedCh := getUngreasedChUa(majorVersion, forkName) + shuffleMap := getGreasedChOrder(len(ungreasedCh), majorVersion) + shuffledCh := make([]string, len(ungreasedCh)) for i, e := range shuffleMap { - shuffledCh[e] = rawCh[i] + shuffledCh[e] = ungreasedCh[i] } return strings.Join(shuffledCh, ", ") } +// It's better to pin on Firefox ESR releases, can have a Firefox ESR version generator later +var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0" + // ChromeUA provides default browser User-Agent based on CPU-seeded PRNG. var AnchoredChromeVersion = ChromeVersion() var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36" -// It would be better to have the three parts ordered randomly upon generation -var ChromeUACH = getGreasedChUa(AnchoredChromeVersion) +var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome") +var MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0" +var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge") func ApplyDefaultHeaders(header http.Header, browser string, variant string) { // Browser-specific @@ -82,8 +92,15 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { header["DNT"] = []string{"1"} header.Set("User-Agent", ChromeUA) header.Set("Accept-Language", "en-US,en;q=0.9") + case "edge": + header["Sec-CH-UA"] = []string{MSEdgeUACH} + header["Sec-CH-UA-Mobile"] = []string{"?0"} + header["Sec-CH-UA-Platform"] = []string{"\"Windows\""} + header["DNT"] = []string{"1"} + header.Set("User-Agent", MSEdgeUA) + header.Set("Accept-Language", "en-US,en;q=0.9") case "firefox": - header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0") // Can have a Firefox ESR version generator later + header.Set("User-Agent", FirefoxUA) header["DNT"] = []string{"1"} header.Set("Accept-Language", "en-US,en;q=0.5") case "go": @@ -96,14 +113,14 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { case "nav": if header.Get("Cache-Control") == "" { switch browser { - case "chrome": + case "chrome", "edge": header.Set("Cache-Control", "max-age=0") } } header.Set("Upgrade-Insecure-Requests", "1") if header.Get("Accept") == "" { switch browser { - case "chrome": + case "chrome", "edge": header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7") case "firefox": header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") @@ -133,7 +150,7 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { header.Set("Sec-Fetch-Site", "same-origin") if header.Get("Priority") == "" { switch browser { - case "chrome": + case "chrome", "edge": header.Set("Priority", "u=1, i") case "firefox": header.Set("Priority", "u=4") @@ -161,6 +178,8 @@ func UseDefaultHeadersWith(header http.Header, variant string) { ApplyDefaultHeaders(header, "chrome", variant) case "!firefox": ApplyDefaultHeaders(header, "firefox", variant) + case "!edge": + ApplyDefaultHeaders(header, "edge", variant) case "!go": ApplyDefaultHeaders(header, "go", variant) } From 680fd538775f2f9ad135084adaf10d0e5361d1a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:05:05 +0000 Subject: [PATCH 43/53] A better dynamic CH UA brand allocation. --- 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 ad3228f6a82a..b8a3dfddf008 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -52,13 +52,15 @@ func getGreasedChOrder(brandLength int, seed int) []int { return []int{} } func getUngreasedChUa(majorVersion int, forkName string) []string { - baseChUa := []string{getGreasedChInvalidBrand(majorVersion), - "\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\""} + // Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice + baseChUa := make([]string, 0, 4) + baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion), + "\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\"") switch forkName { case "chrome": - append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"") + baseChUa = append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"") case "edge": - append(baseChUa, "\"Microsoft Edge\";v=\"" + strconv.Itoa(majorVersion) + "\"") + baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\"" + strconv.Itoa(majorVersion) + "\"") } return baseChUa } From e98393f921878e0e2bb16d9cf55eb4d213d9e6a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:24:14 +0000 Subject: [PATCH 44/53] Turns out a version generator for Firefox... ... isn't quite feasible, as it's heavily dependent on uTLS update cadance. --- common/utils/browser.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index b8a3dfddf008..73e53bb4df44 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -74,10 +74,11 @@ func getGreasedChUa(majorVersion int, forkName string) string { return strings.Join(shuffledCh, ", ") } -// It's better to pin on Firefox ESR releases, can have a Firefox ESR version generator later +// It's better to pin on Firefox ESR releases, and there could be a Firefox ESR version generator later +// However, if the Firefox fingerprint in uTLS doesn't have its update cadence match that of Firefox ESR, then it's better to update the Firefox version manually instead every time a new major ESR release is available var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0" -// ChromeUA provides default browser User-Agent based on CPU-seeded PRNG. +// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG var AnchoredChromeVersion = ChromeVersion() var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36" var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome") From b7697c2e76427cd31a5e695c967e310f24540de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:29:10 +0000 Subject: [PATCH 45/53] Special values in gRPC are entirely different. --- common/utils/browser.go | 14 +++++++------- transport/internet/grpc/dial.go | 11 +++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index 73e53bb4df44..d4c11ad6cff4 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -74,11 +74,11 @@ func getGreasedChUa(majorVersion int, forkName string) string { return strings.Join(shuffledCh, ", ") } -// It's better to pin on Firefox ESR releases, and there could be a Firefox ESR version generator later -// However, if the Firefox fingerprint in uTLS doesn't have its update cadence match that of Firefox ESR, then it's better to update the Firefox version manually instead every time a new major ESR release is available +// It's better to pin on Firefox ESR releases, and there could be a Firefox ESR version generator later. +// However, if the Firefox fingerprint in uTLS doesn't have its update cadence match that of Firefox ESR, then it's better to update the Firefox version manually instead every time a new major ESR release is available. var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0" -// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG +// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG. var AnchoredChromeVersion = ChromeVersion() var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36" var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome") @@ -86,7 +86,7 @@ var MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge") func ApplyDefaultHeaders(header http.Header, browser string, variant string) { - // Browser-specific + // Browser-specific. switch browser { case "chrome": header["Sec-CH-UA"] = []string{ChromeUACH} @@ -107,11 +107,11 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { header["DNT"] = []string{"1"} header.Set("Accept-Language", "en-US,en;q=0.5") case "go": - // Expose the default net/http header + // Expose the default net/http header. header.Del("User-Agent") return } - // variant-specific + // Context-specific. switch variant { case "nav": if header.Get("Cache-Control") == "" { @@ -172,7 +172,7 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { } func UseDefaultHeadersWith(header http.Header, variant string) { - // The global UA special value handler + // The global UA special value handler. if len(header.Values("User-Agent")) < 1 { ApplyDefaultHeaders(header, "chrome", variant) } else { diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 97a8750edc1d..18220958fb17 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -10,6 +10,7 @@ import ( 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/utils" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/grpc/encoding" @@ -190,6 +191,16 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in ) if err == nil { userAgent := grpcSettings.UserAgent + // This section is intentionally different from utils.UseDefaultHeadersWith(). + // It's NOT recommended to set the UA of gRPC connections to that of real browsers, as they are fundamentally incapable of initiating real gRPC connections. + switch userAgent { + case "!chrome": + userAgent = utils.ChromeUA + case "!edge": + userAgent = utils.MSEdgeUS + case "!firefox": + userAgent = utils.FirefoxUA + } setUserAgent(conn, userAgent) conn.Connect() } From 373cc4b282199c58c8143faeadb396a84f5548da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:32:03 +0000 Subject: [PATCH 46/53] Ugh, the cost of one single typo... --- transport/internet/grpc/dial.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 18220958fb17..ec5389ed913b 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -197,7 +197,7 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in case "!chrome": userAgent = utils.ChromeUA case "!edge": - userAgent = utils.MSEdgeUS + userAgent = utils.MSEdgeUA case "!firefox": userAgent = utils.FirefoxUA } From 2cc15e98601f5d959e017d7323ccebadde31688b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 21 Mar 2026 09:57:14 +0000 Subject: [PATCH 47/53] Hopefully not confusing. --- common/utils/browser.go | 8 ++++---- transport/internet/grpc/dial.go | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index d4c11ad6cff4..debf1cd1c5a9 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -177,13 +177,13 @@ func UseDefaultHeadersWith(header http.Header, variant string) { ApplyDefaultHeaders(header, "chrome", variant) } else { switch header.Get("User-Agent") { - case "!chrome": + case "chrome": ApplyDefaultHeaders(header, "chrome", variant) - case "!firefox": + case "firefox": ApplyDefaultHeaders(header, "firefox", variant) - case "!edge": + case "edge": ApplyDefaultHeaders(header, "edge", variant) - case "!go": + case "golang": ApplyDefaultHeaders(header, "go", variant) } } diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index ec5389ed913b..fdb4c52f0e94 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -194,12 +194,14 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in // This section is intentionally different from utils.UseDefaultHeadersWith(). // It's NOT recommended to set the UA of gRPC connections to that of real browsers, as they are fundamentally incapable of initiating real gRPC connections. switch userAgent { - case "!chrome": + case "chrome", "": userAgent = utils.ChromeUA - case "!edge": - userAgent = utils.MSEdgeUA - case "!firefox": + case "firefox": userAgent = utils.FirefoxUA + case "edge": + userAgent = utils.MSEdgeUA + case "golang": + userAgent = "" } setUserAgent(conn, userAgent) conn.Connect() From 6073680f78de2aab18abaedb0987dffd4b6fb87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:26:36 +0000 Subject: [PATCH 48/53] A better name for the method. --- common/utils/browser.go | 2 +- proxy/http/client.go | 2 +- transport/internet/grpc/dial.go | 1 - transport/internet/httpupgrade/dialer.go | 2 +- transport/internet/splithttp/config.go | 2 +- transport/internet/websocket/config.go | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index debf1cd1c5a9..3f6ae4151c0c 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -171,7 +171,7 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { } } -func UseDefaultHeadersWith(header http.Header, variant string) { +func HandleTransportUASettings(header http.Header, variant string) { // The global UA special value handler. if len(header.Values("User-Agent")) < 1 { ApplyDefaultHeaders(header, "chrome", variant) diff --git a/proxy/http/client.go b/proxy/http/client.go index 3fd845aaa04c..83482588f972 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -220,7 +220,7 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u for _, h := range header { req.Header.Set(h.Key, h.Value) } - utils.UseDefaultHeadersWith(req.Header, "nav") + utils.HandleTransportUASettings(req.Header, "nav") connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { req.Header.Set("Proxy-Connection", "Keep-Alive") diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index fdb4c52f0e94..c8b8423c6579 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -191,7 +191,6 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in ) if err == nil { userAgent := grpcSettings.UserAgent - // This section is intentionally different from utils.UseDefaultHeadersWith(). // It's NOT recommended to set the UA of gRPC connections to that of real browsers, as they are fundamentally incapable of initiating real gRPC connections. switch userAgent { case "chrome", "": diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index 59fe61407669..db64be98ca5f 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -96,7 +96,7 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * for key, value := range transportConfiguration.Header { AddHeader(req.Header, key, value) } - utils.UseDefaultHeadersWith(req.Header, "ws") + utils.HandleTransportUASettings(req.Header, "ws") req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "websocket") diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index c8f812428e8b..589d1c1ebe35 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -51,7 +51,7 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Headers { header.Add(k, v) } - utils.UseDefaultHeadersWith(header, "fetch") + utils.HandleTransportUASettings(header, "fetch") return header } diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index b192378e3602..25bb681a23dd 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -24,7 +24,7 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Header { header.Add(k, v) } - utils.UseDefaultHeadersWith(header, "ws") + utils.HandleTransportUASettings(header, "ws") return header } From 663bd1285792f93ce63ff623b1c522d838aa9da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:30:19 +0000 Subject: [PATCH 49/53] One other rename before merging. --- app/dns/nameserver_doh.go | 2 +- app/observatory/burst/ping.go | 2 +- app/observatory/observer.go | 2 +- common/utils/browser.go | 12 ++++++------ transport/internet/reality/reality.go | 2 +- transport/internet/tls/ech.go | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go index 275323027870..40a50f453f68 100644 --- a/app/dns/nameserver_doh.go +++ b/app/dns/nameserver_doh.go @@ -214,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") - utils.ApplyDefaultHeaders(req.Header, "chrome", "fetch") + utils.ApplyMasqueradedHeaders(req.Header, "chrome", "fetch") 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 e880ec954b76..eb7ab4538152 100644 --- a/app/observatory/burst/ping.go +++ b/app/observatory/burst/ping.go @@ -62,7 +62,7 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) { if err != nil { return rttFailed, err } - utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") + utils.ApplyMasqueradedHeaders(req.Header, "chrome", "nav") start := time.Now() resp, err := s.httpClient.Do(req) diff --git a/app/observatory/observer.go b/app/observatory/observer.go index 8e9014c7adbc..ea32598cf5ab 100644 --- a/app/observatory/observer.go +++ b/app/observatory/observer.go @@ -164,7 +164,7 @@ func (o *Observer) probe(outbound string) ProbeResult { probeURL = o.config.ProbeUrl } req, _ := http.NewRequest(http.MethodGet, probeURL, nil) - utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") + utils.ApplyMasqueradedHeaders(req.Header, "chrome", "nav") response, err := httpClient.Do(req) if err != nil { return errors.New("outbound failed to relay connection").Base(err) diff --git a/common/utils/browser.go b/common/utils/browser.go index 3f6ae4151c0c..a6d636de8a32 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -85,7 +85,7 @@ var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome") var MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0" var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge") -func ApplyDefaultHeaders(header http.Header, browser string, variant string) { +func ApplyMasqueradedHeaders(header http.Header, browser string, variant string) { // Browser-specific. switch browser { case "chrome": @@ -174,17 +174,17 @@ func ApplyDefaultHeaders(header http.Header, browser string, variant string) { func HandleTransportUASettings(header http.Header, variant string) { // The global UA special value handler. if len(header.Values("User-Agent")) < 1 { - ApplyDefaultHeaders(header, "chrome", variant) + ApplyMasqueradedHeaders(header, "chrome", variant) } else { switch header.Get("User-Agent") { case "chrome": - ApplyDefaultHeaders(header, "chrome", variant) + ApplyMasqueradedHeaders(header, "chrome", variant) case "firefox": - ApplyDefaultHeaders(header, "firefox", variant) + ApplyMasqueradedHeaders(header, "firefox", variant) case "edge": - ApplyDefaultHeaders(header, "edge", variant) + ApplyMasqueradedHeaders(header, "edge", variant) case "golang": - ApplyDefaultHeaders(header, "go", variant) + ApplyMasqueradedHeaders(header, "go", variant) } } } diff --git a/transport/internet/reality/reality.go b/transport/internet/reality/reality.go index a9bddfed2c60..bc8acd0c9120 100644 --- a/transport/internet/reality/reality.go +++ b/transport/internet/reality/reality.go @@ -223,7 +223,7 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati if req == nil { return } - utils.ApplyDefaultHeaders(req.Header, "chrome", "nav") + utils.ApplyMasqueradedHeaders(req.Header, "chrome", "nav") 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 b874209c9f2d..07afd96ef6d1 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -253,7 +253,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") - utils.ApplyDefaultHeaders(req.Header, "chrome", "fetch") + utils.ApplyMasqueradedHeaders(req.Header, "chrome", "fetch") req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) resp, err := client.Do(req) From 3a2e00f9485c75c3aefdb3f7e4997643c34968fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:41:43 +0000 Subject: [PATCH 50/53] Some corrections. --- common/utils/browser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/utils/browser.go b/common/utils/browser.go index a6d636de8a32..e4f34af91b3c 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -172,7 +172,7 @@ func ApplyMasqueradedHeaders(header http.Header, browser string, variant string) } func HandleTransportUASettings(header http.Header, variant string) { - // The global UA special value handler. + // The global UA special value handler for transports. if len(header.Values("User-Agent")) < 1 { ApplyMasqueradedHeaders(header, "chrome", variant) } else { From 6070bc8cf511487f0232b6c7f380ad65ec450349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:00:20 +0000 Subject: [PATCH 51/53] Still thinks this is a bad idea. Will it compile? --- app/dns/nameserver_doh.go | 2 +- app/observatory/burst/ping.go | 2 +- app/observatory/observer.go | 2 +- common/utils/browser.go | 13 +++++++------ transport/internet/reality/reality.go | 2 +- transport/internet/tls/ech.go | 2 +- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go index 40a50f453f68..bf26b52b23f5 100644 --- a/app/dns/nameserver_doh.go +++ b/app/dns/nameserver_doh.go @@ -214,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") - utils.ApplyMasqueradedHeaders(req.Header, "chrome", "fetch") + utils.HandleTransportUASettings(req.Header, "fetch") 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 eb7ab4538152..6a3429dd0bff 100644 --- a/app/observatory/burst/ping.go +++ b/app/observatory/burst/ping.go @@ -62,7 +62,7 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) { if err != nil { return rttFailed, err } - utils.ApplyMasqueradedHeaders(req.Header, "chrome", "nav") + utils.HandleTransportUASettings(req.Header, "nav") start := time.Now() resp, err := s.httpClient.Do(req) diff --git a/app/observatory/observer.go b/app/observatory/observer.go index ea32598cf5ab..5dcee6a6abe6 100644 --- a/app/observatory/observer.go +++ b/app/observatory/observer.go @@ -164,7 +164,7 @@ func (o *Observer) probe(outbound string) ProbeResult { probeURL = o.config.ProbeUrl } req, _ := http.NewRequest(http.MethodGet, probeURL, nil) - utils.ApplyMasqueradedHeaders(req.Header, "chrome", "nav") + utils.HandleTransportUASettings(req.Header, "nav") response, err := httpClient.Do(req) if err != nil { return errors.New("outbound failed to relay connection").Base(err) diff --git a/common/utils/browser.go b/common/utils/browser.go index e4f34af91b3c..7f70c283f9c9 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -85,7 +85,7 @@ var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome") var MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0" var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge") -func ApplyMasqueradedHeaders(header http.Header, browser string, variant string) { +func applyMasqueradedHeaders(header http.Header, browser string, variant string) { // Browser-specific. switch browser { case "chrome": @@ -173,18 +173,19 @@ func ApplyMasqueradedHeaders(header http.Header, browser string, variant string) func HandleTransportUASettings(header http.Header, variant string) { // The global UA special value handler for transports. + // Just a FYI to whoever needing to fix this piece of code after some spontaneous event, I tried to make the two methods separate to let the code be cleaner and more organized. if len(header.Values("User-Agent")) < 1 { - ApplyMasqueradedHeaders(header, "chrome", variant) + applyMasqueradedHeaders(header, "chrome", variant) } else { switch header.Get("User-Agent") { case "chrome": - ApplyMasqueradedHeaders(header, "chrome", variant) + applyMasqueradedHeaders(header, "chrome", variant) case "firefox": - ApplyMasqueradedHeaders(header, "firefox", variant) + applyMasqueradedHeaders(header, "firefox", variant) case "edge": - ApplyMasqueradedHeaders(header, "edge", variant) + applyMasqueradedHeaders(header, "edge", variant) case "golang": - ApplyMasqueradedHeaders(header, "go", variant) + applyMasqueradedHeaders(header, "go", variant) } } } diff --git a/transport/internet/reality/reality.go b/transport/internet/reality/reality.go index bc8acd0c9120..d9cc003fdef4 100644 --- a/transport/internet/reality/reality.go +++ b/transport/internet/reality/reality.go @@ -223,7 +223,7 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati if req == nil { return } - utils.ApplyMasqueradedHeaders(req.Header, "chrome", "nav") + utils.HandleTransportUASettings(req.Header, "nav") 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 07afd96ef6d1..c78d66ccbe90 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -253,7 +253,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") - utils.ApplyMasqueradedHeaders(req.Header, "chrome", "fetch") + utils.HandleTransportUASettings(req.Header, "fetch") req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) resp, err := client.Do(req) From 74981054c3f192c1b683b938dafac6f62c715433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:21:26 +0000 Subject: [PATCH 52/53] One final rename for clarity. --- app/dns/nameserver_doh.go | 2 +- app/observatory/burst/ping.go | 2 +- app/observatory/observer.go | 2 +- common/utils/browser.go | 4 ++-- proxy/http/client.go | 2 +- transport/internet/httpupgrade/dialer.go | 2 +- transport/internet/reality/reality.go | 2 +- transport/internet/splithttp/config.go | 2 +- transport/internet/tls/ech.go | 2 +- transport/internet/websocket/config.go | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go index bf26b52b23f5..2849dbed3c19 100644 --- a/app/dns/nameserver_doh.go +++ b/app/dns/nameserver_doh.go @@ -214,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") - utils.HandleTransportUASettings(req.Header, "fetch") + utils.TryDefaultHeadersWith(req.Header, "fetch") 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 6a3429dd0bff..2755ec54847b 100644 --- a/app/observatory/burst/ping.go +++ b/app/observatory/burst/ping.go @@ -62,7 +62,7 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) { if err != nil { return rttFailed, err } - utils.HandleTransportUASettings(req.Header, "nav") + utils.TryDefaultHeadersWith(req.Header, "nav") start := time.Now() resp, err := s.httpClient.Do(req) diff --git a/app/observatory/observer.go b/app/observatory/observer.go index 5dcee6a6abe6..c0b38b6e3dd1 100644 --- a/app/observatory/observer.go +++ b/app/observatory/observer.go @@ -164,7 +164,7 @@ func (o *Observer) probe(outbound string) ProbeResult { probeURL = o.config.ProbeUrl } req, _ := http.NewRequest(http.MethodGet, probeURL, nil) - utils.HandleTransportUASettings(req.Header, "nav") + utils.TryDefaultHeadersWith(req.Header, "nav") response, err := httpClient.Do(req) if err != nil { return errors.New("outbound failed to relay connection").Base(err) diff --git a/common/utils/browser.go b/common/utils/browser.go index 7f70c283f9c9..52553191da42 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -171,8 +171,8 @@ func applyMasqueradedHeaders(header http.Header, browser string, variant string) } } -func HandleTransportUASettings(header http.Header, variant string) { - // The global UA special value handler for transports. +func TryDefaultHeadersWith(header http.Header, variant string) { + // The global UA special value handler for transports. Used to be called HandleTransportUASettings. // Just a FYI to whoever needing to fix this piece of code after some spontaneous event, I tried to make the two methods separate to let the code be cleaner and more organized. if len(header.Values("User-Agent")) < 1 { applyMasqueradedHeaders(header, "chrome", variant) diff --git a/proxy/http/client.go b/proxy/http/client.go index 83482588f972..0e50edba3b61 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -220,7 +220,7 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u for _, h := range header { req.Header.Set(h.Key, h.Value) } - utils.HandleTransportUASettings(req.Header, "nav") + utils.TryDefaultHeadersWith(req.Header, "nav") connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { req.Header.Set("Proxy-Connection", "Keep-Alive") diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index db64be98ca5f..571797f6172d 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -96,7 +96,7 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * for key, value := range transportConfiguration.Header { AddHeader(req.Header, key, value) } - utils.HandleTransportUASettings(req.Header, "ws") + utils.TryDefaultHeadersWith(req.Header, "ws") 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 d9cc003fdef4..50b2e02f7d22 100644 --- a/transport/internet/reality/reality.go +++ b/transport/internet/reality/reality.go @@ -223,7 +223,7 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati if req == nil { return } - utils.HandleTransportUASettings(req.Header, "nav") + utils.TryDefaultHeadersWith(req.Header, "nav") 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 589d1c1ebe35..03ed591c2c54 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -51,7 +51,7 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Headers { header.Add(k, v) } - utils.HandleTransportUASettings(header, "fetch") + utils.TryDefaultHeadersWith(header, "fetch") return header } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index c78d66ccbe90..8cfb12512da0 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -253,7 +253,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") - utils.HandleTransportUASettings(req.Header, "fetch") + utils.TryDefaultHeadersWith(req.Header, "fetch") 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 25bb681a23dd..1778c960b819 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -24,7 +24,7 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Header { header.Add(k, v) } - utils.HandleTransportUASettings(header, "ws") + utils.TryDefaultHeadersWith(header, "ws") return header } From a1421979eed366d3b5eef783f127a5b7a76ea0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lumi=C3=A8re=20=C3=89lev=C3=A9?= <88174309+PoneyClairDeLune@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:30:41 +0000 Subject: [PATCH 53/53] Naming coherence adherance. --- 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 52553191da42..2337125a0ed9 100644 --- a/common/utils/browser.go +++ b/common/utils/browser.go @@ -106,7 +106,7 @@ func applyMasqueradedHeaders(header http.Header, browser string, variant string) header.Set("User-Agent", FirefoxUA) header["DNT"] = []string{"1"} header.Set("Accept-Language", "en-US,en;q=0.5") - case "go": + case "golang": // Expose the default net/http header. header.Del("User-Agent") return @@ -185,7 +185,7 @@ func TryDefaultHeadersWith(header http.Header, variant string) { case "edge": applyMasqueradedHeaders(header, "edge", variant) case "golang": - applyMasqueradedHeaders(header, "go", variant) + applyMasqueradedHeaders(header, "golang", variant) } } }