captchaforge 0.2.38

[DO NOT USE — UNDER ACTIVE DEVELOPMENT, NOT PRODUCTION-READY] Captcha solver scaffolding for Firefox + BiDi-driven browsers. The architecture is in place (vendor solvers, retry-loop iframe walking, VLM provider abstraction, real-WAF bench harness) but the live-vendor success rate is still 0% — Cloudflare Turnstile / hCaptcha / reCAPTCHA detect us at a TLS / BiDi fingerprint layer that no flag-based stealth has cleared. Watch the repo; do not depend on this for any real workload.
Documentation
//! End-to-end proof that the reynard ENGINE closes the `navigator.webdriver`
//! tell that plain BiDi Firefox cannot.
//!
//! Companion to `webdriver_native_pref.rs` (which proves the pref is overridden
//! on plain Firefox so native `navigator.webdriver` stays `true`). reynard pins
//! it `false` in the Gecko C++; this drives the real engine and reads the value.
//!
//! Headless. Opt-in via `REYNARD_BIN` (point at a CURRENT-Firefox build — e.g.
//! `…/reynard-staging/camoufox-150…/obj-*/dist/bin/camoufox`; the old Fx133
//! `~/.cache/camoufox` build can't attach to rustenium 1.1.10). Skips cleanly
//! otherwise. `MOZ_DISABLE_CONTENT_SANDBOX=1` recommended.

use captchaforge::stealth::browser::launch_reynard;
use captchaforge::StealthProfile;
use std::time::Duration;

fn reynard_binary() -> Option<String> {
    if let Ok(p) = std::env::var("REYNARD_BIN") {
        if !p.trim().is_empty() {
            return Some(p);
        }
    }
    let home = std::env::var("HOME").ok()?;
    let cached = format!("{home}/.cache/camoufox/camoufox");
    std::path::Path::new(&cached).exists().then_some(cached)
}

async fn within<T>(secs: u64, fut: impl std::future::Future<Output = T>) -> Option<T> {
    tokio::time::timeout(Duration::from_secs(secs), fut).await.ok()
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn reynard_engine_closes_native_webdriver() {
    let Some(bin) = reynard_binary() else {
        eprintln!("SKIP reynard_webdriver: no REYNARD_BIN and no ~/.cache/camoufox/camoufox");
        return;
    };
    eprintln!("[reynard] binary: {bin}");

    let page = match within(90, launch_reynard(&bin, &StealthProfile::FirefoxLinux, true)).await {
        Some(Ok(p)) => p,
        Some(Err(e)) => {
            eprintln!("SKIP reynard_webdriver: launch_reynard failed ({e})");
            return;
        }
        None => {
            eprintln!("SKIP reynard_webdriver: launch_reynard timed out (90s)");
            return;
        }
    };
    eprintln!("[reynard] launched + attached over BiDi");

    if within(25, page.goto("about:blank")).await.is_none() {
        eprintln!("SKIP reynard_webdriver: goto timed out");
        let _ = within(5, page.close()).await;
        return;
    }
    let native = match within(25, page.evaluate("String(navigator.webdriver)")).await {
        Some(Ok(ev)) => ev.into_value::<String>().unwrap_or_else(|_| "<parse-fail>".into()),
        _ => {
            eprintln!("SKIP reynard_webdriver: evaluate timed out / errored");
            let _ = within(5, page.close()).await;
            return;
        }
    };
    eprintln!("[reynard] native navigator.webdriver = {native}");
    let _ = within(8, page.close()).await;

    assert_eq!(
        native, "false",
        "the reynard engine must read native navigator.webdriver=false even under BiDi \
         — the webDriverAdvanced tell plain Firefox cannot hide."
    );
}