File size: 4,462 Bytes
b0cb964
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
## v6.2 β€” Full Chrome TLS Impersonation (BoringSSL)

### Critical Fix: Complete Browser Fingerprint Impersonation

The v6.1 fixes (Chrome headers, HTTP/2, cookies) were necessary but **not sufficient**.
Cloudflare uses **three fingerprinting layers simultaneously**, and v6.1 only addressed one:

| Layer | What CF Checks | v6.1 Status | v6.2 Status |
|-------|---------------|-------------|-------------|
| **HTTP Headers** | UA, Sec-CH-UA, Sec-Fetch-* | βœ… Fixed | βœ… (now via rquest) |
| **TLS Fingerprint (JA3/JA4)** | Cipher suites, extensions, GREASE | ❌ FAILED (rustls) | βœ… Fixed (BoringSSL) |
| **HTTP/2 Fingerprint (Akamai)** | SETTINGS frame values, window size | ❌ FAILED (hyper defaults) | βœ… Fixed (Chrome H2) |

### Root Cause

`reqwest` + `rustls` generates a TLS ClientHello that is **nothing like Chrome's**:
- `rustls` sends ~6 cipher suites in wrong order. Chrome sends 16 with GREASE.
- `rustls` doesn't support `compress_certificate` (brotli). Chrome does.
- `rustls` doesn't inject GREASE values. Chrome uses RFC 8701 GREASE.
- `hyper`'s HTTP/2 sends `INITIAL_WINDOW_SIZE=65535`. Chrome sends `6291456` (6MB).
- `hyper` sends SETTINGS that produce a different Akamai H2 fingerprint.

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.

### Solution: `rquest` with BoringSSL

Replaced `reqwest` (rustls) with `rquest` (BoringSSL-backed Chrome impersonation):

```toml
# OLD (v6.1):
reqwest = { version = "0.12", features = ["rustls-tls", "gzip", "brotli", "cookies", "http2"] }

# NEW (v6.2):
rquest = { version = "1.0", features = ["full"] }
```

**`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.

One-line initialization:
```rust
let client = Client::builder()
    .impersonate(Impersonate::Chrome137)
    .cookie_store(true)
    .build()?;
```

This single call configures:
- βœ… TLS cipher suite order (16 ciphers + GREASE, exact Chrome order)
- βœ… TLS extension ordering (with compress_certificate, ALPS, GREASE)
- βœ… HTTP/2 SETTINGS frame (INITIAL_WINDOW_SIZE=6291456, etc.)
- βœ… Header ordering (sec-ch-ua before accept, as Chrome does)
- βœ… User-Agent (Chrome 137)
- βœ… GREASE values (random per-connection, as Chrome does)
- βœ… Certificate compression (brotli, zlib)
- βœ… ALPN (h2, http/1.1)

### What This Means in Practice

| Site | v6.1 (rustls) | v6.2 (BoringSSL) |
|------|---------------|-------------------|
| Cloudflare Basic | ⚠️ Sometimes | βœ… Always passes |
| Cloudflare JS Challenge | ❌ Blocked | βœ… Cookie-based pass |
| DataDome | ❌ Blocked | βœ… Passes (JA3 matches) |
| PerimeterX/HUMAN | ❌ Blocked | βœ… Passes (JA3+H2 match) |
| Akamai Bot Manager | ❌ Blocked | βœ… Passes (H2 FP match) |
| Cloudflare Turnstile | ❌ Needs WebView | ❌ Still needs WebView |

### Changes

**`crates/bex-core/Cargo.toml`**:
- Removed `reqwest` with `rustls-tls` feature
- Added `rquest = { version = "1.0", features = ["full"] }`
- Version bumped to 2.1.0

**`crates/bex-core/src/http_service.rs`**: Complete rewrite:
- Uses `rquest::Client` with `Impersonate::Chrome137`
- Removed all manual header construction (`browser_default_headers()` deleted)
- Headers are now managed by the impersonation layer automatically
- Plugin headers still override when explicitly set (for custom Referer, etc.)
- Auto-Referer from URL origin still works
- Cache logic unchanged

### Build Requirements

`rquest` bundles BoringSSL which requires:
```bash
# Linux:
apt-get install cmake clang

# macOS:
brew install cmake llvm
```

The build handles BoringSSL compilation automatically via the `boring` crate.

### Migration Notes for Plugin Authors

**No changes needed.** The WIT interface (`http::send-request`) is unchanged.
Plugins still set their own headers via `Request.headers` β€” these override
the Chrome defaults when explicitly provided.

The only behavioral difference: requests that previously got CF-challenged
or blocked will now succeed transparently. No code changes needed in plugins.

### Known Limitation

Cloudflare Turnstile (interactive CAPTCHA) still requires actual human interaction.
This is by design β€” Turnstile uses browser fingerprinting + proof-of-work that
cannot be solved programmatically without a full WebView.