captchaforge 0.2.21

Automatic CAPTCHA detection and multi-strategy solving for chromiumoxide-driven headless browsers (Cloudflare Turnstile, reCAPTCHA v2/v3, hCaptcha, image grids, audio, sliders).
Documentation
# captchaforge Architecture

## Overview

captchaforge is a Rust library for automatic CAPTCHA detection and multi-strategy solving in headless Chromium browsers. It is built on top of [`chromiumoxide`](https://github.com/mattsse/chromiumoxide) (Chrome DevTools Protocol) and operates entirely through CDP — no browser extensions, no external APIs required.

## Extension surface

Three layers of extensibility, lightest to heaviest:

1. **TOML rules (`detect::rules`)** — drop a `[[provider]]` block in a TOML file (or extend the bundled `rules/community.toml`). Engine generates a deterministic, escaped JS probe from the rule's selectors / window-globals / script-src triggers. Zero Rust required. Bundled pack: AWS WAF, DataDome, Akamai, PerimeterX/HUMAN, Arkose, Geetest v3+v4, Friendly Captcha, MTCaptcha, WP math captchas.
2. **`CaptchaProvider` trait (`provider.rs`)** — bundles a `Detector` impl with the recommended `SolveMethod` ordering for that vendor. New captchas implement `Detector` + `CaptchaProvider` in a single file under `src/detect/`. The `ProviderRegistry::with_built_in_rules()` constructor returns built-in providers PLUS the bundled rule pack, all priority-sorted.
3. **`Detector` trait (`detect.rs`)** — the underlying contract. Used directly when the rule layer can't express the detection (CDP probes, async waits, behavioural fingerprinting).

## Tier-A operational config

- **`captchaforge::config::Config`** — TOML-driven operational config (`.captchaforge.toml`). Compiled defaults → file → builder. Discovery walks `$CAPTCHAFORGE_CONFIG``./.captchaforge.toml``./captchaforge.toml``$XDG_CONFIG_HOME/captchaforge/config.toml`. `deny_unknown_fields` so typos surface as parse errors.
- **`Config::build_chain()`** — single canonical chain constructor used by both `auto_solve()` and the CLI's `solve` subcommand: chain config + token cache + provider registry + Behavioral + VLM (env/TOML-aware) + Audio + Third-party. Drop a `.captchaforge.toml` in your project and the one-call API picks it up automatically.

## Telemetry, cache, persistence

- **`telemetry::SolverTelemetry`** — implement to capture every solver attempt as a `SolveEvent { solver, captcha_type, kind, domain, outcome, time_ms, confidence, method }`. Default is `NoopTelemetry`. Cache hits also fire as `Success` with `solver: "TokenCache"` so dashboards distinguish solved-from-scratch from cache-served. Install via `chain.with_telemetry(Arc::new(...))`.
- **`solver::TokenCache`** — per-`(domain, captcha_type)` solved-token cache with configurable TTL (60s default). Backed by `Mutex<HashMap>` so reads / writes are O(1) without an extra dep. Cache entries carry both the solved token AND the cookies captured at the original solve, so a cache hit replays the trusted session — without it the next request lands on a fresh session that re-triggers the captcha. Install via `chain.with_token_cache(Arc::new(TokenCache::new()))`.
- **`solver::PatternStore`** — per-domain learned routing with bounded-α EMA (α = 1/n bootstrap for first 10 samples, then α = 0.2). Per-method stats so vendor migrations flip the winner within ~10 fresh samples instead of being drowned out by historical wins. `save_to_path` / `load_from_path` (atomic JSON via temp+rename), `merge` (cross-instance share, higher `sample_count` wins).
- **`solver::ThirdPartyCaptchaSolver`** — adapter against the 2captcha-compatible HTTP protocol used by 2captcha, CapMonster, CapSolver. Fail-fast on terminal API errors (bad key, zero balance, banned IP) so polling doesn't burn 150s on a doomed task. `supports()` returns false without an API key so the solver is skipped instead of issuing 401s.

```
┌──────────────────────────────────────────────────────────────────────┐
│                        Consumer Code                                  │
│         (wafrift, gossan, sear, captchaforge-cli, …)                  │
│      ┌───────────────────────────────┐                                │
│      │ captchaforge::auto_solve(page)│  (one-call convenience)        │
│      └───────────────────────────────┘                                │
└──────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────┐
│                       CaptchaSolverChain                              │
│   ┌────────────────────────────────────────────────────────────┐     │
│   │  TokenCache (60s TTL)  ──hit──►  return CrowdSourced        │     │
│   └────────────────────────────────────────────────────────────┘     │
│                              │ miss                                  │
│                              ▼                                       │
│   ┌────────────────────────────────────────────────────────────┐     │
│   │  ProviderRegistry — for Custom kinds, filter solvers by     │     │
│   │  provider.recommended_solver_methods. For built-in kinds,   │     │
│   │  filter by solver.supports().                               │     │
│   └────────────────────────────────────────────────────────────┘     │
│                              │                                       │
│                              ▼                                       │
│   ┌────────────────────────────────────────────────────────────┐     │
│   │  PatternStore — re-order eligible solvers, best-method     │     │
│   │  for this domain runs first.                                │     │
│   └────────────────────────────────────────────────────────────┘     │
│                              │                                       │
│                              ▼                                       │
│   ┌─────────────┐ ┌──────────┐ ┌───────────┐ ┌────────────┐ ┌────────┐
│   │ Behavioral  │ │ VLM      │ │ Audio     │ │ ThirdParty │ │ Human  │
│   │ (mouse sim) │ │ (vision) │ │ (STT)     │ │ (2captcha) │ │(screen)│
│   └─────────────┘ └──────────┘ └───────────┘ └────────────┘ └────────┘
│         │              │            │              │                 │
│         ▼              ▼            ▼              ▼                 │
│   ┌────────────────────────────────────────────────────────────┐     │
│   │  SolverTelemetry  ◄─── one event per attempt, all outcomes  │     │
│   └────────────────────────────────────────────────────────────┘     │
│         │              │            │              │                 │
│         └──────────────┴────────────┴──────────────┘                 │
│   on success: capture cookies → cache + CaptchaSolveResult.cookies   │
└──────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────┐
│                      detect::detect()                                 │
│   ┌────────────────────────────────────────────────────────────┐     │
│   │  Built-in detectors (11) — turnstile, recaptcha, hcaptcha,  │     │
│   │  image, audio, pow, slider, canvas, multi_step, shadow_dom, │     │
│   │  challenge_page. One file each under src/detect/.           │     │
│   └────────────────────────────────────────────────────────────┘     │
│   ┌────────────────────────────────────────────────────────────┐     │
│   │  detect::rules — TOML-driven RuleDetectors. Bundled         │     │
│   │  community.toml: AWS WAF, DataDome, Akamai, PerimeterX/     │     │
│   │  HUMAN, Arkose, Geetest v3+v4, Friendly, MTCaptcha,         │     │
│   │  WP math captchas. Surface as DetectedCaptcha::Custom(name).│     │
│   └────────────────────────────────────────────────────────────┘     │
└──────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────┐
│                    chromiumoxide::Page                                │
│              ┌──────────────┐  ┌──────────────┐                      │
│              │ Main Frame   │  │ iframe #1    │                      │
│              │              │  │ (cross-origin)│                      │
│              └──────────────┘  └──────────────┘                      │
│                      ▲                ▲                              │
│                      └──── frame.rs ──┘                              │
│         evaluate_in_all_frames() / verify_token_in_frames()           │
│         + cookies::capture_from_page() / apply_to_page()              │
└──────────────────────────────────────────────────────────────────────┘
```

## Module Breakdown

### `detect` — CAPTCHA Identification
- **Approach**: Per-detector JS payload evaluated against the page via `Runtime.evaluate`. Each detector is its own module under `src/detect/`.
- **Coverage**: 11 built-in detectors (Turnstile, reCAPTCHA v2/v3, hCaptcha, image, audio, PoW, slider, canvas, multi-step, shadow-DOM, Cloudflare interstitial) + 10 bundled TOML rules (AWS WAF, DataDome, Akamai, PerimeterX/HUMAN, Arkose, Geetest v3+v4, Friendly Captcha, MTCaptcha, WP math captchas).
- **Output**: `CaptchaInfo { kind, site_key, page_url, container_selector }`. `kind` is `DetectedCaptcha` (`#[non_exhaustive]`, includes `Custom(String)` for TOML-rule providers).
- **Drift prevention**: `detect/sink_registry.rs` is a single source-of-truth dangerous-function catalog consumed by both the AST taint engine (in wptrace) and the regex source scanner — same pattern applied here means built-in detectors and TOML rules can't drift.
- **Legacy**: `DETECT_JS` remains as a single monolithic IIFE for callers that don't want the registry.

### `solver` — Multi-Strategy Solving
- **`CaptchaSolver` trait**: Pluggable strategy interface.
- **`BehavioralCaptchaSolver`**: Realistic mouse movements (Bézier curves), click delays, typing cadence.
- **`VlmCaptchaSolver`**: Screenshots → multimodal LLM (Ollama-compatible) for grid/text/click CAPTCHAs.
- **`AudioCaptchaSolver`**: Audio challenge → STT endpoint → transcribed answer.
- **`CaptchaSolverChain`**: Tries solvers in order, re-orders based on `PatternStore` history. Honours an installed `TokenCache` (skip-the-chain on cache hit) and `SolverTelemetry` (one event per attempt). `chain.cached_solution(info)` exposes the cache short-circuit standalone.

### `frame` — Cross-Origin Iframe Piercing
- **Problem**: Sandbox iframes block `contentDocument` access with `SecurityError`
- **Solution**: Uses CDP `Runtime.evaluate` with per-frame `ExecutionContextId`
- **Key functions**: `evaluate_in_all_frames`, `find_element_centre_in_frames`, `verify_token_in_frames`

### `behavior` — Human Simulation
- `mouse_move_bezier`: Cubic Bézier with 8–20 control points, ease-in-out timing
- `click_realistic`: 50–150ms hold delay
- `type_realistic`: 30–80ms key press, 80–250ms inter-key, occasional 200–600ms thinking pause
- `scroll_realistic`: 3–8 steps with ±20px jitter

## Data Flow

1. Consumer calls `detect(page).await`
2. Detection JS runs in page context → returns JSON
3. If `is_captcha(&info)`, consumer calls `chain.solve(page, &info).await`
4. Chain consults `PatternStore` for historically-best method
5. Solvers attempt in priority order:
   - Behavioral: find checkbox via frame piercing → click → poll for token
   - VLM: screenshot → POST to LLM → parse coordinates/text → interact
   - Audio: click audio button → capture audio → POST to STT → submit
6. Result is verified via `verify_token_in_frames` before `success: true`
7. Result recorded in `PatternStore` for future ordering

## Thread Safety

- `PatternStore`: `Arc<RwLock<HashMap<...>>>` — safe for concurrent reads/writes
- `CaptchaSolver` trait requires `Send + Sync` — all solvers are thread-safe
- `CaptchaSolverChain`: Immutable after construction; solve is `&self`

## Security Considerations

- `unsafe_code` is forbidden at the crate level
- No credential leakage (previously fixed: removed debug `println!` from scanner)
- Token verification before claiming success prevents false positives
- VLM endpoint and model are configurable (not hardcoded)