captchaforge 0.2.40

Captcha detection and solving for Firefox and BiDi-driven browsers. Detection, vendor solver scaffolding, trusted cross-origin click delivery into nested OOPIFs, and stealth personas are implemented and tested; broad live-vendor solve rates are not yet benchmarked.
Documentation
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# captchaforge Revised Roadmap v2: Zero API Cost

**Status:** Post-wafrift-benchmark reassessment. WAF bypass (harder problem, bounty-eligible) solved in months. CAPTCHA solving (easier problem, automation-only) is an engineering project.

**Constraint:** ZERO external API costs. All inference runs locally on available GPUs (RTX 5090, 4090, 3080 Ti). No 2Captcha, no CapSolver, no Anti-Captcha, no cloud LLM APIs.

**Target:** 3-4 months to a pure self-hosted CAPTCHA solver. No research project cop-outs.

---

## ★ North Star: beat browser-use on Stealth Bench V1

**Goal (set 2026-06-05):** significantly beat browser-use's own stealth benchmark
(https://github.com/browser-use/benchmark. `Stealth_Bench_V1.enc`, 71 tasks).

**What the bench actually measures** (from browser-use.com/posts/stealth-benchmark):
71 **real anti-bot-protected sites**, 3 steps each (search → visit → read a link),
an LLM judge (`gemini-2.5-flash`) scoring only **"was the agent blocked?"**. Grouped
by vendor:

| Vendor | Sites | Browser-Use bypass |
|---|---|---|
| Cloudflare | 23 | 93% |
| PerimeterX | 18 | 81% |
| Datadome | 13 | |
| Akamai | 8 | 85% |
| reCaptcha | 6 | |
| Kasada / Shape | 3 | |

**Control baselines (this is a fingerprint+behavior+network test, not a captcha test):**
headless Chromium **2%**, headful Chromium **50%**. Top provider Browser-Use Cloud **81%**;
bottom half ~40%. Weak providers all leak the *same* fingerprint: "headful Linux Chromium"
= stock Chromium + residential proxy + a CAPTCHA-solver extension.

### Why our stack can win (the categorical advantage)

Every provider in the field uses **Chromium** (patched headless or a fork). guise drives a
**real Firefox over WebDriver BiDi** (`foxdriver`). That is not a cosmetic difference:

1. **Authentic TLS + HTTP/2 fingerprint for free.** A real Firefox emits a genuine
   Firefox JA3/JA4 ClientHello and HTTP/2 SETTINGS/priority fingerprint. Cloudflare and
   Akamai weight TLS/HTTP2 coherence heavily; patched Chromium routinely has a TLS↔JS
   mismatch, a real Firefox never does.
2. **No CDP.** CDP is the single most-fingerprinted automation surface (Runtime.enable
   leak, `cdc_` globals, the DevTools WS). BiDi has none of it, a whole class of detectors
   is N/A for us.
3. **The residual JS tells are exactly what guise now closes** (this session):
   - stealth preload actually applies (was a silent no-op): `navigator.webdriver` hidden;
   - every override reports `[native code]` via a `Function.prototype.toString` proxy (toString tamper tell closed);
   - the disguise is **family-coherent** with real Firefox (vendor `""`, Gecko UA,
     `productSub 20100101`, no `window.chrome`/`usb`/`PaymentRequest`/…), proven by a
     live probe gate that is now **GREEN** (0 criticals, 120/126) on headless Firefox 151;
   - human input dispatches **trusted** via BiDi (`isTrusted=true`), not synthetic JS.

### Gap list to actually win (prioritized)

1. **Real measurement harness: ✅ LANDED (first cut).** New bench suite
   `bench/src/suites/stealth_gauntlet.rs` + Tier-B catalogue `bench/data/stealth_gauntlet.json`:
   drives the FULL disguise (`prepare_page`) **vs bare** (two-pair) against real public
   bot-detection / fingerprint scorers and reports, per site, "was the browser judged a bot?"
The captchaforge analogue of Stealth Bench V1. Deterministic per-site JS judges (not an
   LLM); uncertain → `unknown`, never a false pass. IP-independent scorers (areyouheadless,
   sannysoft) isolate fingerprint quality; IP-confounded ones (browserscan, Cloudflare
   `nowsecure.nl`) are labelled. Run: `cargo run --bin captchaforge-bench -- --suite
   stealth-gauntlet --with-network`.
   Also landed: the **real 80 Stealth-Bench-V1 sites** decrypted into
   `bench/data/stealth_bench_sites.json` (load via `STEALTH_GAUNTLET_CATALOGUE=…`), and
   **foxdriver proxy support** (`FoxBrowserConfig.proxy` / `ProxyConfig::from_url`, emits
   `network.proxy.*` prefs; bench `--proxy` / `STEALTH_PROXY`). The gauntlet found + drove
   fixes for THREE real disguise tells (webdriver `undefined``false`; plugins-not-PluginArray
   override removed; webdriver also defined on the navigator *instance* = own property `_.has`
   detects → prototype-only). **Result: the disguised Firefox now passes BOTH IP-independent
   real detectors, areyouheadless + sannysoft = 2/2** (bare is blocked by sannysoft; disguised
   has 0 real fails (only `window.chrome` "Chrome (New)", which a real Firefox also fails)).
   Probe gate green 122/128.
   Authenticated residential proxies: **DONE**: `proxywire::relay` (`AuthRelay`,
   `spawn_auth_relay[_for_endpoint]`) is a local CONNECT-proxy that fronts a credentialed upstream
   (reuses `connect_via_route` for the authed HTTP-CONNECT/SOCKS5 tunnel); Firefox points at the
   local relay (no auth). Validated end-to-end vs a mock authenticated upstream (3 tests: positive
   inject+tunnel, negative wrong-auth-blocked, parse). Wired: `harness::resolve_proxy` auto-spawns
   it when `--proxy`/`STEALTH_PROXY` carries `user:pass@`; pool + gauntlet bare control both use it.
   **So the whole pipeline is code-complete.** The ONLY remaining input for the 80-site number is a
   real **residential proxy URL** (external asset): `STEALTH_PROXY=socks5://user:pass@host:port
   STEALTH_GAUNTLET_CATALOGUE=bench/data/stealth_bench_sites.json cargo run --bin captchaforge-bench
   -- --suite stealth-gauntlet --with-network`.
2. **Headless-Firefox coherence audit**: the bench runs headless; real FF `--headless`
   differs from headful (WebGL via llvmpipe, AudioContext, media codecs, `outerHeight`).
   The JS probe gate is green, but Cloudflare uses more than JS. Audit + close headless tells.
   - **Media-codec coherence, measured + now gated.** A new `guise` diagnostic
     (`guise/tests/headless_tells.rs`) dumps the absolute headless surface. Ground truth on a
     **GPU+ffmpeg host**: headless FF151 is clean: `canPlayType` h264/aac/av1/vp9/opus all
     `"probably"`, `mseH264 true`, `eme "function"`, WebGL reports the real NVIDIA GPU (NOT
     llvmpipe), AudioContext 48k/2ch. So the stripped-codec / llvmpipe tells the roadmap feared
     do **not** manifest on a real-GPU box (confirming the "run headful on a real GPU" guidance).
     They DO manifest on a codec-less cloud image, which the differential gate cannot catch
     (it diffs two identical engines). Closed that blind spot: new **absolute** codec-coherence
     probe `guise/src/probe/codec.rs` (`media: supported-codec matrix matches a consumer
     browser`) folded into `probes_for` for every family, flags a stripped H.264/AAC/MSE/EME
     matrix as Critical/Drift. 13 unit tests; live-Pass proven by composition (diagnostic matrix
     × `healthy_*_matrix_passes`).
3. **Behavioral wiring + trusted-input audit**: route guise `human` (trusted
   mouse/keyboard/scroll/timing) into the *navigation* flow so PerimeterX/Datadome behavioral
   models see human motion.
   - **`warmup.rs` was dispatching SYNTHETIC `new MouseEvent` + `dispatchEvent` (isTrusted=false)**
     and `window.scrollBy`: for the one module whose entire job is to look human to the passive
     scorers that gate on `isTrusted`. A burst of `isTrusted:false` events is a *stronger* bot tell
     than no motion, so warmup was counterproductive. **FIXED**: every pointer/scroll routes
     through the trusted BiDi helpers (`behavior::mouse_move_human` / `scroll_realistic`), deleting
     the duplicated bezier (`mouse_move_smooth`). Wired into production via `sdk.rs:103`
     (`apply_default_warmup`). Proven live: `tests/warmup_trusted.rs::warm_session_emits_only_trusted_events`
     (only isTrusted=true, ≥1 mousemove).
   - **`solver/slider.rs::drag_handle` had the same bug**, the slider drag (the *most*
     isTrusted-gated challenge: GeeTest / AWS WAF / DataDome puzzle) was synthetic `new
     MouseEvent('mousedown'|'mousemove'|'mouseup')`. **FIXED**: trusted press→bezier-moves→release
     (`mouse_down`/`move_mouse_to`/`mouse_up`), same trajectory. Proven live:
     `tests/warmup_trusted.rs::drag_primitives_emit_only_trusted_events`.
   - **Still untrusted (concrete, tracked):** (a) `solver/yolo_grid.rs:246` + `solver/vlm.rs:580`
     dispatch `new MouseEvent(t,…)` on reCAPTCHA image-grid tiles (vision-gated; needs a
     cross-iframe trusted `click_at_in(frame_ctx, x, y)`: validate against live reCAPTCHA before
     changing). (b) `solver/behavioral.rs:348,423` fire `dispatchEvent(new Event('click'/'change'))`
     on captcha-shaped checkboxes, a deliberate broad sweep for *simple* check-to-pass UIs that
     runs ALONGSIDE the trusted primary path (`click_turnstile`/`click_recaptcha_v2`     `click_realistic` → trusted `click_at`); keep for simple UIs but consider skipping it once a
     real Turnstile/reCAPTCHA is detected so it can't register an untrusted touch on the gated
     widget. (c) `input`/`change` after value-set (multi_step/crnn/math/audio/vlm) is acceptable 
     form frameworks require those events and `isTrusted` is essentially never gated on them.
4. **HTTP/2 + TLS on the browser path**: confirm the real-Firefox handshake reaches the
   target unaltered when a proxy is in front (a MITM proxy destroys the advantage).
5. **Residential proxy / IP reputation**: infra, but decisive for these vendors.

**Do NOT** pivot to a patched-Chromium stack to match the field, the real-Firefox path is
the differentiator. Deepen it.

---

## Guiding Principle

CAPTCHA solving is not about beating Google's ML models. It's about building automation that does what a human does:

1. **Image CAPTCHAs:** See the image, identify the objects. Vision problem. Solved with off-the-shelf models.
2. **Behavioral challenges (Turnstile, reCAPTCHA v3):** Look like a human browser. Stealth engineering problem. Patched Chromium + fingerprint evasion + behavioral noise.
3. **Edge cases:** Local LLM reasoning (Llama 3.3 70B, Qwen2.5-VL) for novel challenge types. No external APIs.

The market validates this: WAF bypasses get bounties (CumulusFire). CAPTCHA "bypasses" don't, because passing a CAPTCHA is intended behavior. The only gap is automation. With local GPUs, the compute gap against Google doesn't exist.

---

## Phase 0: Delete Dead Weight (Week 1) ✅

**Goal:** Remove 1,506 LOC of duplication and broken code. Make captchaforge a focused CAPTCHA-solving tool, not a WAF-evasion tool.

| File | LOC | Action | Replacement |
|------|-----|--------|-------------|
| `src/tls_proxy.rs` | 674 | DELETE | `scanclient::tls_impersonate` |
| `src/waf_detect.rs` | 558 | DELETE | `erroracle` (if needed by consumers) |
| `src/proxy_pool.rs` | 274 | DELETE | `proxywire` |

**Verify:**
- `cargo build` passes after deletion
- `wafrift_captchaforge_bridge` still compiles
- No regressions in `captchaforge::detect` or `captchaforge::solver`

**Deliverable:** Clean build, focused codebase, no broken consumers.

---

## Phase 1: Browser Stealth Stack (Weeks 2-6) ✅

**Goal:** Make the headless browser pass Cloudflare Turnstile / reCAPTCHA v3 transparently. The challenge JS should execute, evaluate the browser as legitimate, and set `cf_clearance` without any explicit "solving."

### 1.1 Patched Chromium Integration ✅

**Current state:** `chromium_sidecar.rs` (486 LOC) detects patched Chromium binaries but doesn't fully integrate them.

**Work needed:**
- Extend `chromium_sidecar.rs` to launch patched Chromium with CDP connection
- Patched Chromium must override:
  - Canvas fingerprint (consistent per-profile noise)
  - WebGL fingerprint (vendor/renderer strings)
  - Navigator properties (`webdriver`, `plugins`, `languages`)
  - Screen/window dimensions (consistent with TLS fingerprint profile)
  - Audio context fingerprint
  - Font enumeration
- The patch set can be sourced from existing projects (Camoufox, undetected-chromedriver patches) and adapted for CDP-based control

**Key insight:** We don't need to invent patches. The community has solved headless detection. We need to integrate the patches into our CDP-based stack.

### 1.2 Behavioral Simulation ✅

**Current state:** `keystroke_timing.rs` (14 LOC shim), `mobile_webview.rs` (137 LOC), `mobile_screenshot.rs` (198 LOC).

**Work needed:**
- Move behavioral simulation to `stealth` lib (where it belongs per MASTER_PLAN)
- Implement realistic mouse movement patterns (bezier curves, acceleration, overshoot)
- Implement realistic scroll patterns (momentum scrolling, pauses)
- Implement realistic keyboard timing (variable keypress intervals, typo simulation)
- Implement realistic page interaction (random pauses between actions, reading-time simulation)
- All behavior is profile-driven: `stealth-profiles` defines "this Chrome 131 user types at 180 WPM with 2% typo rate"

**Integration point:** `captchaforge::browser::StealthSession` wraps `runtime_headless::BrowserRuntime` and injects behavior scripts via CDP before any page interaction.

### 1.3 TLS + Browser Profile Consistency ✅

**Current state:** captchaforge uses chromiumoxide but lacks scanclient TLS-impersonate integration.

**Work needed:**
- Wire `scanclient::tls_impersonate` as the HTTP transport for any non-CDP requests
- Ensure TLS fingerprint matches browser profile (Chrome 131 TLS → Chrome 131 browser)
- `stealth::fingerprint::browser_catalog` provides the canonical mapping

**Deliverable:** A browser session that passes Cloudflare's browser integrity check on first visit, no challenge triggered.

---

## Phase 2: Vision Backend for Image CAPTCHAs (Weeks 7-10) ✅ COMPLETE

**Goal:** Solve image-based CAPTCHAs (reCAPTCHA v2 checkbox, hCaptcha image grids, classic text CAPTCHAs) with vision models.

### 2.1 CAPTCHA Type Detection ✅

**Done:** Detection already classifies `ImageGrid` (reCAPTCHA v2 / hCaptcha), `TextChallenge`, `Checkbox`, `Invisible`, and `Audio` via DOM selectors + heuristic analysis.

### 2.2 Vision Model Pipeline ✅

**Architecture:**
```text
Challenge screenshot → Object detection (YOLOv8) → Grid tile mapping → CDP clicks
                     → CRNN recognition → Text answer → CDP input → Submit
```

**Done:**
- `ort` 2.0 rc.12 integrated with CUDA/TensorRT/CPU fallback (`vision` feature flag)
- `YoloDetector` in `src/vision/yolo.rs`: full ONNX inference pipeline:
  - Letterbox resize, NCHW tensor construction, YOLOv8 output parsing
  - NMS post-processing, COCO class name mapping (80 classes)
  - `task_to_coco_classes()` maps reCAPTCHA task text → COCO classes
  - Inference: ~85ms cold, ~66ms warm on CPU
- `CrnnRecognizer` in `src/vision/crnn.rs`: CRNN text recognition:
  - CTC decoding, default alphanumeric charset (0-9, A-Z + blank)
  - Loads ONNX model from `<cache>/captchaforge/models/crnn_text/crnn_text.onnx` (`<cache>` = `dirs::cache_dir()`, e.g. `~/.cache` on Linux)
  - Training script: `scripts/train_crnn.py` (PyTorch → ONNX export)
- `ModelHub` in `src/vision/model_hub.rs`: downloads and caches models
  - YOLOv8n (~12MB) auto-downloaded from Ultralytics releases
  - CRNN model trained locally via Python script
- Feature-gated: compiles without `vision` (solvers are inert, chain falls back to VLM)

### 2.3 Click Automation ✅

**Done:**
- `YoloGridSolver` in `src/solver/yolo_grid.rs`: wired into solver chain at position 14:
  - Lazy model loading (no blocking in constructor)
  - Extracts task text from page DOM
  - Maps task → COCO classes, falls back to VLM for unknown classes
  - Takes screenshot, runs YOLO detection
  - Maps detections to grid tiles (3×3, 4×4 auto-detected)
  - Injects clicks via CDP (mouse event dispatch on tile elements)
  - Clicks verify button, polls for token
- `CrnnTextSolver` in `src/solver/crnn_text.rs`: wired into chain at position 15:
  - Lazy model loading
  - Screenshot → CRNN inference → types text into input field
  - Clicks submit, polls for token
- `tokio::sync::Mutex` for async-safe detector access

### 2.4 Testing ✅

**Test coverage:**
- `tests/vision_integration.rs`: 4 tests (model download, YOLO load+inference, CRNN load, task mapping)
- `tests/vision_e2e.rs`: 9 tests (mock image grid, mock text CAPTCHA, chain wiring, click verification, benchmarks)
- `tests/vision_property.rs`: 7 property tests (random image robustness, empty image, small image, task mapping exhaustiveness, model hub paths)
- `src/vision/yolo.rs`: unit tests (COCO names, task mapping, IoU calculation, integration with real model)
- `src/vision/crnn.rs`: unit tests (charset length)
- All tests pass: `cargo test --lib --features vision` → 697 passed

**Benchmarks:**
- YOLO cold inference: <200ms on CPU
- YOLO warm inference: <100ms on CPU
- CRNN inference: <50ms on CPU (tiny model)

**Deliverable:** Image CAPTCHAs solved end-to-end with local vision models. 20 vision-specific tests, all passing.

---

## Phase 3: Local LLM Reasoning Backend (Week 11)

**Goal:** Handle edge cases (novel challenge types, complex reasoning tasks) with local LLMs. Zero external API calls.

### 3.1 LLM Engine

**Hardware allocation:**
- RTX 5090 (32GB VRAM): Llama 3.3 70B Q4_K_M (~40GB → fits with CPU offload layers) or Qwen2.5-VL 72B for vision+reasoning
- RTX 4090 (24GB VRAM): Qwen2.5-VL 7B / 32B for fast vision inference, or secondary Llama instance
- RTX 3080 Ti (12GB VRAM): YOLOv8 vision inference, lightweight model serving

**Implementation:**
- `llama.cpp` server via `llama-cpp-2` Rust bindings or raw HTTP to local server
- `vllm` for batched inference if throughput matters
- Model management: download on first run, cache in `dirs::cache_dir()/captchaforge/models/` (`~/.cache/captchaforge/models/` on Linux)

### 3.2 Prompt Engineering for CAPTCHA Types

**Image grid ("select all traffic lights"):**
```
You are solving a CAPTCHA challenge. The image shows a 3x3 grid.
Describe each cell: what objects are visible?
Return ONLY the cell numbers (1-9) that contain [target_object].
```

**Text challenge (distorted text):**
```
Read the text in this image. Ignore noise and distortion.
Return ONLY the characters shown.
```

**Novel challenge (unknown format):**
```
Analyze this challenge screenshot. What action is required to proceed?
Options: click_checkbox, select_images, type_text, click_verify, wait.
Return the action and any required parameters.
```

### 3.3 Fallback Logic

```rust
match solve_vision(&page, &challenge_info).await {
    Ok(result) => result,
    Err(SolveError::VisionModelUncertain) => {
        // Screenshot → local LLM → reasoning → action
        solve_via_llm(&page, &challenge_info, &llm_engine).await
    }
    Err(other) => Err(other),
}
```

**Deliverable:** Novel challenge types handled by local LLM reasoning without any external API.

---

## Phase 4: wafrift Bridge Integration (Week 12)

**Goal:** Make the wafrift-captchaforge-bridge actually work.

**Current state:** Bridge compiles but produces 0% solutions at runtime.

### 4.1 Bridge Fixes

**Work needed:**
- Update bridge to use `captchaforge::browser::StealthSession` instead of raw `runtime_headless::BrowserRuntime`
- Ensure TLS impersonate is active for all non-CDP traffic
- Pass `BridgeConfig` through to solver (headless mode, timeout, sandbox)
- Harvest cookies after challenge resolution: `cf_clearance`, `_abck`, `ak_bmsc`, `aws-waf-token`
- Add bridge integration tests against a local mock challenge server

### 4.2 End-to-End Validation

**Test targets:**
1. Local mock challenge server (controlled environment)
2. CumulusFire WAF challenge page (if it serves Turnstile)
3. Real Cloudflare-protected site (operator's own test domain)

**Success criteria:**
- >50% success rate on local mock challenges
- `cf_clearance` cookie harvested on Cloudflare challenges
- No crashes, no hangs, clean browser teardown

**Deliverable:** wafrift + captchaforge working together as a single stealth + challenge-resolution pipeline.

---

## Phase 5: Packaging & Distribution (Weeks 13-14)

**Goal:** captchaforge is installable and usable as both a library and a CLI.

### 5.1 CLI

```bash
captchaforge solve --url https://example.com/challenge --provider local
captchaforge solve --url https://example.com/challenge --provider capsolver --api-key $KEY
captchaforge detect --screenshot challenge.png
captchaforge train --dataset ./labeled_captchas/ --epochs 50
```

### 5.2 Library API

```rust
use captchaforge::{StealthBrowser, ChallengeDetector, LocalSolver, ApiSolver};

let browser = StealthBrowser::new(Profile::Chrome131).await?;
let page = browser.goto("https://example.com").await?;
let challenge = ChallengeDetector::detect(&page).await?;
let result = LocalSolver::default().solve(&page, &challenge).await?;
```

### 5.3 Feature Flags

- `vision`: ONNX runtime + YOLO models
- `api-fallback`: 2Captcha/CapSolver/Anti-Captcha clients
- `stealth`: patched Chromium + behavioral simulation
- `bridge`: wafrift integration

**Deliverable:** `cargo install captchaforge` works. Library consumers can pick features.

---

## Success Metrics

| Metric | Phase 0 | Phase 1 | Phase 2 | Phase 3 | Phase 4 | Phase 5 |
|--------|---------|---------|---------|---------|---------|---------|
| Build passes |||||||
| LOC (post-dedup) | ~49K | ~52K | ~56K | ~60K | ~60K | ~60K |
| Mock challenge success | N/A | >70% | >80% | >85% | >90% | >95% |
| Real CF challenge cookie | N/A | >30% | >50% | >65% | >75% | >85% |
| Vision backend (YOLO+CRNN) | N/A | N/A |||||
| Vision tests | N/A | N/A | 20 | 20 | 20 | 20 |
| wafrift bridge working | N/A | N/A | N/A | N/A |||
| CLI installable | N/A | N/A | N/A | N/A | N/A ||

---

## Risk Mitigation

| Risk | Mitigation |
|------|-----------|
| Patched Chromium is unstable | Start with Camoufox/undetected-chromedriver patches, adapt gradually. Keep unpatched fallback. |
| Vision model accuracy low | Synthetic training data first, real data second. VLM fallback covers gaps. |
| Cloudflare updates detection | Same as wafrift, continuous dogfooding, stealth lib is shared across tools. |
| ONNX runtime build issues | Use `ort` with prebuilt binaries. GPU is optional. |
| Cloudflare updates detection | Same as wafrift, continuous dogfooding, stealth lib is shared across tools. |
| LLM inference latency | 70B model on 5090: ~10-20s per query. Acceptable for CAPTCHA solving. Use 7B/32B models for fast path, 70B for edge cases. |
| Model download size | 70B Q4 = ~40GB. Download once, cache forever. Ship with download script, not in repo. |

---

## Open Questions

1. **Patched Chromium source:** Maintain own fork or track Camoufox/undetected-chromedriver upstream?
2. **Model serving:** `llama.cpp` server (simple) vs `vllm` (batched, faster)?
3. **Training pipeline:** Python (PyTorch) for training, Rust (`ort`) for inference. Acceptable split?
4. **Vision + LLM orchestration:** Screenshot → YOLO (GPU) → if uncertain → LLM (GPU). Single pipeline or separate services?
5. **Commercial model:** Open-source tool with paid stealth profiles? Per-seat license for enterprise?

---

*Roadmap written after deep wafrift source analysis + WAF bypass tool market research + CAPTCHA market research. WAF bypass is the harder problem. CAPTCHA solving is feasible in 3-4 months as focused engineering.*

---

## Empirical finding: 2026-06-05: the "cross-origin click delivery gap" is a misdiagnosis

The README's 0% table attributes the hCaptcha/reCAPTCHA-v2 failures to a
"cross-origin click delivery gap" (checkbox click "fails to deliver into the
cross-origin anchor frame"). This was tested directly and **disproven** at the
input layer.

- **Oracle:** `libs/runtime/foxdriver/tests/cross_origin_click.rs`: deterministic,
  no vendor, no fingerprint. Two HTTP origins (different ports) host a real
  cross-origin iframe whose checkbox posts to the top window only on a
  `event.isTrusted` click; a Turnstile-shaped doubly-nested variant too.
- **Result (Firefox 151 + foxdriver BiDi):** a trusted top-context viewport click
  (`Page::click_at`) delivers `isTrusted=true` into a single AND a doubly-nested
  cross-origin OOPIF. A new frame-targeted `Page::click_at_in` does the same in
  the iframe's own context. A synthetic JS `.click()` arrives `isTrusted=false`.

**Implication:** the click lands trusted. The 0% is **downstream of the click 
the fingerprint / risk-scoring layer** (TLS JA3/JA4, navigator/CDP coherence in
guise's `fingerprint` module). Instrument the token issuance / risk verdict, not
the pointer. Stop attributing interactive-vendor failures to click delivery.

**Bug found + fixed in passing:** guise's human-mouse **trajectory** was dispatched
as untrusted synthetic JS (`document.dispatchEvent(new MouseEvent('mousemove'))`,
`isTrusted=false`) in `guise::human::mouse_driver::dispatch_mouse_moved` (live via
`MouseDriver`) and `behavior::mouse_move_bezier`: nullifying the anti-bot value of
the path. Fixed by routing every sampled point through a new trusted BiDi primitive
`foxdriver::Page::move_mouse_to`. Proven by the contract test's trusted-mousemove
assertion.

### Follow-up: 2026-06-05: the fingerprint stealth was itself a no-op (now fixed)

Pursuing the "0% is fingerprint" conclusion above led to the bigger root cause:
**the entire JS browser stealth never applied.** guise's own live gate
(`libs/runtime/guise/tests/probe_live.rs`) showed bare Firefox = 23 detection
criticals and "stealthed" = 23, zero removed; `navigator.webdriver` stayed
`true`.

Cause: stealth is injected via WebDriver BiDi `script.addPreloadScript`, whose
`functionDeclaration` is **invoked as a function**, but the scripts are IIFE
bodies `(() => {…})()` (→ `undefined`, not callable), so BiDi never ran them.
captchaforge's own `src/stealth_profiles.rs` has the same pattern (and the same
swallowed-error `let _ =`).

Fix: `foxdriver::Page::add_preload_script` now wraps the body in `() => { … }`.
After it, in `probe_live`: `navigator.webdriver` → hidden, automation-globals
clean, `hardwareConcurrency`/UA/platform applied. Regression guard:
`guise/tests/stealth_core_tells.rs`. captchaforge's stealth path benefits
automatically (it routes through the same foxdriver call).

**Still open (the real anti-detection depth):**
- Override getters leak via `Function.prototype.toString` (`() => undefined`,
  non-native) (needs toString spoofing).
- `probe_live` is Chrome-biased (scores Firefox against `window.chrome.*`,
  `navigator.usb/hid/serial`, `PaymentRequest`, …) so it can't go green for a
  Firefox disguise until expectations are made profile-aware.
- guise and captchaforge duplicate `FIREFOX_STEALTH_JS`: dedupe.