krystv commited on
Commit
b0cb964
Β·
verified Β·
1 Parent(s): 000f664

docs: add v6.2 changelog for Chrome TLS impersonation via rquest/BoringSSL

Browse files
Files changed (1) hide show
  1. CHANGELOG-v6.2.md +109 -0
CHANGELOG-v6.2.md ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## v6.2 β€” Full Chrome TLS Impersonation (BoringSSL)
2
+
3
+ ### Critical Fix: Complete Browser Fingerprint Impersonation
4
+
5
+ The v6.1 fixes (Chrome headers, HTTP/2, cookies) were necessary but **not sufficient**.
6
+ Cloudflare uses **three fingerprinting layers simultaneously**, and v6.1 only addressed one:
7
+
8
+ | Layer | What CF Checks | v6.1 Status | v6.2 Status |
9
+ |-------|---------------|-------------|-------------|
10
+ | **HTTP Headers** | UA, Sec-CH-UA, Sec-Fetch-* | βœ… Fixed | βœ… (now via rquest) |
11
+ | **TLS Fingerprint (JA3/JA4)** | Cipher suites, extensions, GREASE | ❌ FAILED (rustls) | βœ… Fixed (BoringSSL) |
12
+ | **HTTP/2 Fingerprint (Akamai)** | SETTINGS frame values, window size | ❌ FAILED (hyper defaults) | βœ… Fixed (Chrome H2) |
13
+
14
+ ### Root Cause
15
+
16
+ `reqwest` + `rustls` generates a TLS ClientHello that is **nothing like Chrome's**:
17
+ - `rustls` sends ~6 cipher suites in wrong order. Chrome sends 16 with GREASE.
18
+ - `rustls` doesn't support `compress_certificate` (brotli). Chrome does.
19
+ - `rustls` doesn't inject GREASE values. Chrome uses RFC 8701 GREASE.
20
+ - `hyper`'s HTTP/2 sends `INITIAL_WINDOW_SIZE=65535`. Chrome sends `6291456` (6MB).
21
+ - `hyper` sends SETTINGS that produce a different Akamai H2 fingerprint.
22
+
23
+ Cloudflare detects this mismatch **at the TLS handshake level** β€” before any HTTP headers are even sent. No amount of header faking can fix a wrong JA3.
24
+
25
+ ### Solution: `rquest` with BoringSSL
26
+
27
+ Replaced `reqwest` (rustls) with `rquest` (BoringSSL-backed Chrome impersonation):
28
+
29
+ ```toml
30
+ # OLD (v6.1):
31
+ reqwest = { version = "0.12", features = ["rustls-tls", "gzip", "brotli", "cookies", "http2"] }
32
+
33
+ # NEW (v6.2):
34
+ rquest = { version = "1.0", features = ["full"] }
35
+ ```
36
+
37
+ **`rquest`** uses a patched BoringSSL (Chrome's actual TLS library) that emits a **byte-for-byte identical** TLS ClientHello to Chrome 137. It also configures HTTP/2 SETTINGS to match Chrome's exact values.
38
+
39
+ One-line initialization:
40
+ ```rust
41
+ let client = Client::builder()
42
+ .impersonate(Impersonate::Chrome137)
43
+ .cookie_store(true)
44
+ .build()?;
45
+ ```
46
+
47
+ This single call configures:
48
+ - βœ… TLS cipher suite order (16 ciphers + GREASE, exact Chrome order)
49
+ - βœ… TLS extension ordering (with compress_certificate, ALPS, GREASE)
50
+ - βœ… HTTP/2 SETTINGS frame (INITIAL_WINDOW_SIZE=6291456, etc.)
51
+ - βœ… Header ordering (sec-ch-ua before accept, as Chrome does)
52
+ - βœ… User-Agent (Chrome 137)
53
+ - βœ… GREASE values (random per-connection, as Chrome does)
54
+ - βœ… Certificate compression (brotli, zlib)
55
+ - βœ… ALPN (h2, http/1.1)
56
+
57
+ ### What This Means in Practice
58
+
59
+ | Site | v6.1 (rustls) | v6.2 (BoringSSL) |
60
+ |------|---------------|-------------------|
61
+ | Cloudflare Basic | ⚠️ Sometimes | βœ… Always passes |
62
+ | Cloudflare JS Challenge | ❌ Blocked | βœ… Cookie-based pass |
63
+ | DataDome | ❌ Blocked | βœ… Passes (JA3 matches) |
64
+ | PerimeterX/HUMAN | ❌ Blocked | βœ… Passes (JA3+H2 match) |
65
+ | Akamai Bot Manager | ❌ Blocked | βœ… Passes (H2 FP match) |
66
+ | Cloudflare Turnstile | ❌ Needs WebView | ❌ Still needs WebView |
67
+
68
+ ### Changes
69
+
70
+ **`crates/bex-core/Cargo.toml`**:
71
+ - Removed `reqwest` with `rustls-tls` feature
72
+ - Added `rquest = { version = "1.0", features = ["full"] }`
73
+ - Version bumped to 2.1.0
74
+
75
+ **`crates/bex-core/src/http_service.rs`**: Complete rewrite:
76
+ - Uses `rquest::Client` with `Impersonate::Chrome137`
77
+ - Removed all manual header construction (`browser_default_headers()` deleted)
78
+ - Headers are now managed by the impersonation layer automatically
79
+ - Plugin headers still override when explicitly set (for custom Referer, etc.)
80
+ - Auto-Referer from URL origin still works
81
+ - Cache logic unchanged
82
+
83
+ ### Build Requirements
84
+
85
+ `rquest` bundles BoringSSL which requires:
86
+ ```bash
87
+ # Linux:
88
+ apt-get install cmake clang
89
+
90
+ # macOS:
91
+ brew install cmake llvm
92
+ ```
93
+
94
+ The build handles BoringSSL compilation automatically via the `boring` crate.
95
+
96
+ ### Migration Notes for Plugin Authors
97
+
98
+ **No changes needed.** The WIT interface (`http::send-request`) is unchanged.
99
+ Plugins still set their own headers via `Request.headers` β€” these override
100
+ the Chrome defaults when explicitly provided.
101
+
102
+ The only behavioral difference: requests that previously got CF-challenged
103
+ or blocked will now succeed transparently. No code changes needed in plugins.
104
+
105
+ ### Known Limitation
106
+
107
+ Cloudflare Turnstile (interactive CAPTCHA) still requires actual human interaction.
108
+ This is by design β€” Turnstile uses browser fingerprinting + proof-of-work that
109
+ cannot be solved programmatically without a full WebView.