# CloakBrowser for .NET A complete, faithful **.NET 8 / C#** port of the [CloakBrowser](https://github.com/CloakHQ/CloakBrowser) Python wrapper - stealth Chromium that passes bot-detection tests, built on top of [`Microsoft.Playwright`](https://playwright.dev/dotnet/). CloakBrowser is a thin wrapper around a closed-source, source-level patched Chromium binary (58 C++ fingerprint patches). This port reproduces **all** of the wrapper functionality with identical behavior - same launch flags, same proxy / GeoIP / WebRTC logic, and a humanize layer whose curves, timings, and stealth paths match the Python and JavaScript clients exactly. --- ## Table of contents - [Why a decorator layer?](#why-a-decorator-layer) - [Feature matrix](#feature-matrix) - [Requirements](#requirements) - [Installation](#installation) - [Project layout](#project-layout) - [Quick start](#quick-start) - [Humanized interactions (transparent)](#humanized-interactions-transparent) - [What changes vs. plain Playwright](#what-changes-vs-plain-playwright) - [Explicit `HumanPage` engine (advanced)](#explicit-humanpage-engine-advanced) - [Launch API reference](#launch-api-reference) - [Options reference](#options-reference) - [`HumanConfig` reference](#humanconfig-reference) - [How the humanize layer works](#how-the-humanize-layer-works) - [Mouse motion (Bezier)](#mouse-motion-bezier) - [Human typing & the CDP stealth path](#human-typing--the-cdp-stealth-path) - [Non-ASCII input](#non-ascii-input) - [Scrolling](#scrolling) - [Actionability checks](#actionability-checks) - [Shared timeout budget (#307)](#shared-timeout-budget-307) - [Focus-aware press / clear](#focus-aware-press--clear) - [Proxy, GeoIP & WebRTC](#proxy-geoip--webrtc) - [CLI](#cli) - [Environment variables](#environment-variables) - [Building & testing](#building--testing) - [Test suite map](#test-suite-map) - [Mapping to the Python source](#mapping-to-the-python-source) - [License](#license) --- ## Why a decorator layer? .NET's Playwright exposes **sealed interfaces** (`IPage`, `ILocator`, `IMouse`, ...) that **cannot** be monkey-patched the way the Python/JS clients replace methods at runtime. CloakBrowser bridges this gap with a **transparent decorator layer**: 1. You pass `Humanize = true` at launch. 2. `NewPageAsync()` / `NewContextAsync()` return a **wrapped** object. 3. Every standard Playwright call (`page.ClickAsync`, `page.FillAsync`, `page.Mouse.MoveAsync`, ...) is automatically humanized - **no API changes** in your code. 4. The wrappers are generated at **compile time** by a Roslyn source generator (`[GenerateInterfaceDelegation]`), so they are fully statically typed with **no reflection on the hot path**. Non-intercepted members are delegated verbatim. ``` your code ──> HumanizedPage ──┬─ intercepted member ─> humanize engine ─> raw IPage (IPage) (generated) └─ everything else ─────────────────────> raw IPage (verbatim) ``` --- ## Feature matrix | Capability | Status | Source | | --- | :---: | --- | | Automatic binary **download / cache / auto-update** (SHA-256 verified) | ✅ | `Download.cs`, `Config.cs` | | **Stealth launch args** (random fingerprint seed, platform spoofing) | ✅ | `CloakLauncher.cs` | | **Proxy** - HTTP/HTTPS + SOCKS5, inline URL-encoded credentials | ✅ | `ProxyResolver.cs` | | **GeoIP** timezone/locale from proxy exit IP (MaxMind GeoLite2) | ✅ | `GeoIp.cs` | | **WebRTC** IP spoofing (`--fingerprint-webrtc-ip=auto`) | ✅ | `CloakLauncher.cs` | | **Widevine** CDM hint seeding (Linux) | ✅ | `Widevine.cs` | | **Persistent contexts** (profile reuse) | ✅ | `CloakLauncher.cs` | | **Humanize** - Bezier mouse, human typing, scrolling, actionability | ✅ | `Human/`, `Wrappers/` | | Transparent humanize via **source generator** | ✅ | `CloakBrowser.Generators/` | | **CLI** (`install` / `info` / `update` / `clear-cache`) | ✅ | `CloakBrowser.Cli/` | --- ## Requirements | Dependency | Version | Why | | --- | --- | --- | | .NET SDK | **8.0** | target framework `net8.0` | | `Microsoft.Playwright` | **1.49.0** | underlying browser automation | | `MaxMind.GeoIP2` | **5.2.0** | GeoIP timezone/locale resolution | --- ## Installation ```bash dotnet add package CloakBrowser ``` The patched Chromium binary downloads automatically on first launch, cached under `~/.cloakbrowser` (override with `CLOAKBROWSER_CACHE_DIR`). --- ## Project layout ``` dotnet/ ├── CloakBrowser.sln ├── src/ │ ├── CloakBrowser/ # the library │ │ ├── Config.cs # <- cloakbrowser/config.py │ │ ├── Download.cs # <- cloakbrowser/download.py (+ zip-slip guard) │ │ ├── GeoIp.cs # <- cloakbrowser/geoip.py │ │ ├── Widevine.cs # <- cloakbrowser/widevine.py │ │ ├── CloakLauncher.cs # <- cloakbrowser/browser.py (launch funcs, build_args) │ │ ├── ProxyResolver.cs # <- cloakbrowser/browser.py (proxy URL helpers) │ │ ├── ProxySettings.cs # <- ProxySettings TypedDict │ │ ├── LaunchOptions.cs # launch option records │ │ ├── Handles.cs # CloakBrowserHandle / CloakContextHandle │ │ ├── CloakLog.cs # logging facade │ │ ├── Human/ # <- cloakbrowser/human/* │ │ │ ├── HumanConfig.cs # <- human/config.py (HumanConfig + merge) │ │ │ ├── HumanRandom.cs # <- human/config.py (rand/sleep helpers) │ │ │ ├── HumanMouse.cs # <- human/mouse.py (Bezier engine) │ │ │ ├── HumanKeyboard.cs # <- human/keyboard.py (typing + CDP stealth) │ │ │ ├── HumanScroll.cs # <- human/scroll.py │ │ │ ├── Actionability.cs # <- human/actionability.py │ │ │ ├── IsolatedWorld.cs # <- _AsyncIsolatedWorld │ │ │ ├── PlaywrightAdapters.cs # IMouse/IKeyboard/ICDPSession -> raw protocols │ │ │ └── HumanPage.cs # <- patch_page flows (explicit engine) │ │ └── Wrappers/ # transparent humanize decorators (Humanize=true) │ │ ├── Humanize.cs # wrap entry points + helpers (idempotent) │ │ ├── HumanCursor.cs # shared per-page cursor / CDP stealth state │ │ ├── LocatorHumanizer.cs # locator-based humanize engine │ │ ├── HumanizedBrowser.cs # IBrowser decorator │ │ ├── HumanizedBrowserContext.cs # IBrowserContext decorator │ │ ├── HumanizedPage.cs # IPage decorator │ │ ├── HumanizedFrame.cs # IFrame decorator │ │ ├── HumanizedLocator.cs # ILocator decorator │ │ ├── HumanizedElementHandle.cs # IElementHandle decorator │ │ ├── HumanizedMouse.cs # IMouse decorator │ │ └── HumanizedKeyboard.cs # IKeyboard decorator │ ├── CloakBrowser.Generators/ # Roslyn source generator │ │ └── InterfaceDelegationGenerator.cs # emits delegating members │ └── CloakBrowser.Cli/ # <- cloakbrowser/__main__.py ├── examples/CloakBrowser.Examples/ # runnable examples (see below) └── tests/CloakBrowser.Tests/ # xUnit tests ``` --- ## Quick start ```csharp using CloakBrowser; await using var browser = await CloakLauncher.LaunchAsync(new LaunchOptions { Headless = true, }); var page = await browser.NewPageAsync(); await page.GotoAsync("https://bot.incolumitas.com/"); Console.WriteLine(await page.TitleAsync()); ``` --- ## Humanized interactions (transparent) Pass `Humanize = true` and **write ordinary Playwright code** - mouse moves, clicks, typing, and scrolling are humanized automatically. Nothing else in your code changes: ```csharp using CloakBrowser; using CloakBrowser.Human; await using var browser = await CloakLauncher.LaunchAsync(new LaunchOptions { Headless = false, Humanize = true, // <- the only change HumanPreset = HumanPreset.Careful, // optional: Default | Careful HumanConfig = new Dictionary { ["typing_delay"] = 90.0 }, // optional overrides }); var page = await browser.NewPageAsync(); // returns a transparently-wrapped IPage await page.GotoAsync("https://example.com/login"); await page.FillAsync("#username", "alice"); // per-character human typing await page.FillAsync("#password", "s3cr3t!"); // (variable delays, thinking pauses, typos+fixes) await page.ClickAsync("button[type=submit]"); // Bezier mouse curve to a realistic aim point await page.Mouse.WheelAsync(0, 600); // accelerate -> cruise -> decelerate scroll ``` The wrapping is **complete and transitive** - anything you reach through the page is wrapped too: ```csharp await page.Mouse.MoveAsync(400, 300); // humanized await page.Keyboard.TypeAsync("hello"); // humanized var btn = page.Locator("#submit"); // wrapped ILocator await btn.ClickAsync(); // humanized foreach (var frame in page.Frames) { /* wrapped IFrame */ } ``` ### What changes vs. plain Playwright | Member | `Humanize = false` (default) | `Humanize = true` | | --- | --- | --- | | `IPage` / `ILocator` `ClickAsync`, `DblClickAsync`, `HoverAsync`, `TapAsync` | direct Playwright dispatch | Bezier-curve mouse move to a randomized in-element aim point, then click | | `IPage` / `ILocator` `FillAsync`, `TypeAsync` / `PressSequentiallyAsync` | instant value set / fast type | per-character typing with variable delays, thinking pauses, occasional typos that self-correct | | `IPage` / `ILocator` `PressAsync` | direct | human key timing; clicks to focus first only if not already focused | | `ILocator` `CheckAsync`, `UncheckAsync`, `SetCheckedAsync`, `DragToAsync` | direct | humanized click / curved drag | | `ILocator` `SelectOptionAsync` (all overloads) | instant native select | curved hover to the `