Skip to content
Browse docs

Native Chromium Fork

BetterWright can run the pinned BetterWright Chromium 150 fork while keeping its public run(), human.*, captcha.*, snapshot, policy, proxy, and vault APIs unchanged. On macOS arm64 and Linux x64, betterwright setup / betterwright update download the fork into the zero-config discovery root; CloakBrowser remains the fallback when no artifact is present (and the only path on Windows today).

Install / update

betterwright update          # download fork → ~/.betterwright/chromium/
betterwright update --force  # re-fetch + re-verify even if already present
betterwright setup           # fork (when shipped) + CloakBrowser fallback
betterwright setup --cloak-only

Artifacts come from the GitHub Release tag chromium-<version> (see CHROMIUM_FORK_RELEASE_TAG / CHROMIUM_FORK_ASSETS in src/chromium-fork.ts). Each zip is SHA-256 pinned in that manifest before extract. Apple-licensed fonts are not in the public zip.

Runtime Selection

Use an exact executable path:

export BETTERWRIGHT_CHROMIUM_PATH=/absolute/path/to/chrome
betterwright run -c 'return await page.title()'

Or point at the packaged artifact root:

export BETTERWRIGHT_CHROMIUM_ROOT=/absolute/path/to/artifacts
betterwright run -c 'return await page.title()'

The artifact-root layout is fixed:

artifacts/
  mac-arm64/Chromium.app/Contents/MacOS/Chromium
  linux-x64/chrome
  win-x64/chrome.exe

Only macOS arm64 and Linux x64 are built and runtime-tested. Windows is reserved for a future verified artifact.

BETTERWRIGHT_CHROMIUM_PATH takes precedence over BETTERWRIGHT_CHROMIUM_ROOT. Configured paths must be absolute and must exist; BetterWright fails closed instead of silently falling back to another browser.

Zero-Config Discovery and Platform Routing

With neither variable set, BetterWright checks the default root ~/.betterwright/chromium/ for the current platform's artifact. Found → the fork runs with no configuration at all. Not found (or no artifact shipped for the platform, e.g. Windows) → managed CloakBrowser, exactly as before.

~/.betterwright/chromium/
  mac-arm64/Chromium.app/Contents/MacOS/Chromium
  linux-x64/chrome          (+ fonts/ttf/ for the macOS-metric font set)

This makes mixed fleets trivial: run betterwright update (or setup) on Linux and macOS hosts, betterwright setup --cloak-only on Windows, and every machine picks the right backend with the same configuration. Set BETTERWRIGHT_CHROMIUM_ROOT=off (or BETTERWRIGHT_CHROMIUM_PATH=off) to force the managed path on a host that has the artifact installed.

Profiles are not interchangeable. Fork and Cloak share $BETTERWRIGHT_HOME/browser/profile by default. Prefer a dedicated home for fork deployments (e.g. BETTERWRIGHT_HOME=~/.betterwright-fork). Switching off after the fork has upgraded that profile requires deleting browser/profile first.

Match timezone/locale to egress (or enable geoip with upstreamProxy). Nothing in the fork hard-codes Singapore or any other region — pin whatever geography the exit IP actually has.

Control Plane

The fork is launched through stock playwright-core over normal CDP. Custom behavior lives in Chromium/V8 (deferred inspector console delivery). No page-world stealth shim is installed. Patchright/stealthRuntimeFix is rejected when the fork is configured.

Identity

OptionNative behavior
locale--lang and --fingerprint-locale
timezone--bw-timezone (alias --fingerprint-timezone)
platform--fingerprint-platform; defaults to macos (see masking below)
headlessReal native headless
headedInvisibleHeaded window parked off-screen
upstreamProxy / geoipPolicy-checked egress; locale/tz from egress when enabled

Platform Masking (Linux → macOS)

A headless-Linux browser identity is one of the strongest automation signals risk engines score, so the fork masks the host platform as a realistic consumer Mac by default (platform: "macos"; opt out with platform: "linux" or "windows" in the constructor / --platform= on the CLI).

The identity is not invented — every value was captured from genuine Google Chrome 150.0.7871.129 (the fork's exact pinned version) on an Apple M4 Pro MacBook Pro running macOS 26.6: UA and UA-CH (brands, macOS 26.6.0, arm), navigator.platform = MacIntel, 1800×1169 @2x screen geometry with real menu-bar/Dock availHeight, 12 cores, deviceMemory 16. See src/fork-identity.ts.

Two layers apply it without any page-world JavaScript shims:

  1. Launch layer--fingerprint-platform, window-size/DPR flags, and a context-level userAgent baseline (correct from the first navigation).
  2. CDP emulation layer — per-page Emulation.setUserAgentOverride with full UserAgentMetadata (the DevTools protocol path, invisible to getter/toString probes), plus hardware-concurrency override where the build supports it.

The binary patch set in chromium-fork-patches.md is implemented and verified: UA/UA-CH at the source, navigator.platform, WebGL renderer/vendor, deterministic per-profile canvas/audio farbling, screen geometry, and a bundled macOS-metric font set loaded through a launch-time FONTCONFIG_FILE. With timezone/locale matched to egress geography the headless Linux fork returns real Google SERPs instead of /sorry.

Cloaking V2 identity coherence still applies; see cloaking-v2.md.