# 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)