Documentation menu

Canvas bridge

Render canvas and WebGL on a real remote GPU so the pixels a page reads back are coherent with the GPU your profile claims. Experimental and opt-in — with no --canvas-bridge-url, Clearcote renders entirely locally, exactly as before.

Why it exists

Clearcote renders canvas/WebGL on whatever GPU the host machine actually has. If a profile claims a different GPU than the host, strict anti-detect / browser-tampering checks that compare the rendered pixels against the claimed hardware can notice the mismatch — you cannot make one GPU emit another GPU's exact pixels in software. The canvas bridge removes the mismatch: instead of rendering locally and only spoofing the GPU string, it forwards the canvas/WebGL operations to a remote host that has the GPU you want to present, and returns that host's real pixels. Because it forwards the operations (not a fixed library of pre-recorded images), it handles arbitrary and procedurally-generated canvases — not just known probes.

How it works

The readback APIs — getImageData, toDataURL, readPixels and measureText — return the bridge host's authentic pixels; the local farbling noise is bypassed on the bridge path (the bridge pixels are ground truth). Transport is a WebSocket carrying a compact binary message stream, and the persona seed (--fingerprint) is sent to the server so it selects a matching GPU profile.

text
  clearcote (your automation host)         bridge host (real GPU)
  +-----------------------------+          +-------------------------+
  | page: getImageData /        |  ops --> | headless clearcote      |
  |       toDataURL / readPixels |         | renders on the real GPU,|
  | CanvasBridgeClient  <-- pixels ---------|  reads back the pixels  |
  +-----------------------------+          +-------------------------+

What you need

  • A bridge host — a Windows machine with a real GPU that does the rendering. Its GPU becomes the canvas identity your profiles present, so pick hardware matching the persona you want to show (an NVIDIA box to present NVIDIA, and so on).
  • A private network path between your automation host and the bridge host. The bridge speaks plaintext WebSocket (ws://) — always run it over a private network or an encrypted tunnel (Tailscale, WireGuard, or SSH port forwarding). Never expose the bridge port on the public internet.

Setup

1. Start the render server on the real-GPU host. It's a small Python coordinator that launches a headless Clearcote and replays the forwarded ops on its real canvas, so the pixels it returns are exactly what a real Clearcote on that GPU produces. The render host's real GPU becomes the canvas/WebGL identity.

bash
pip install playwright    # one-time
python tools/canvas-bridge-server/server.py \
    --backend local \
    --chrome /path/to/clearcote/chrome.exe \
    --port 8443

# Or drive a browser already exposed over DevTools:
# python tools/canvas-bridge-server/server.py \
#     --backend cdp \
#     --cdp-url ws://127.0.0.1:9222/devtools/browser/... \
#     --port 8443

2. Tunnel it. ws:// is unencrypted — put both hosts on the same Tailscale/WireGuard network, or forward the port over SSH:

bash
ssh -N -L 8443:localhost:8443 user@bridge-host
# the bridge is now reachable at ws://127.0.0.1:8443

3. Launch the client (your automation Clearcote) pointing at the bridge:

bash
--canvas-bridge-url=ws://127.0.0.1:8443 \
--canvas-bridge-auth=user:secret \
--no-sandbox \
--fingerprint=<seed>
FlagMeaning
--canvas-bridge-urlBridge endpoint ws://host:port. Required to enable the bridge.
--canvas-bridge-authuser:secret HTTP Basic credentials; must match the server.
--no-sandboxRequired — the client opens the bridge socket from the renderer process, which the sandbox blocks.
--fingerprintYour persona; the seed is sent to the server to pick a matching GPU profile.
--canvas-bridge-modePer-origin policy: off, all (default), allow, or deny.
--canvas-bridge-allow / --canvas-bridge-denyComma-separated eTLD+1 lists used by mode=allow or mode=deny.
--canvas-bridge-fallbackCold cache-miss behavior: block waits for the bridge; local serves local pixels instead of stalling.

From the SDK

SDK 0.9.0 exposes a first-class canvasBridge / canvas_bridge option. Setting a bridge URL emits the switches and auto-adds --no-sandbox. For the same allow-list policy in a full launch script, see Examples.

javascript
const browser = await clearcote.launch({
  fingerprint: "user-1",
  canvasBridge: {
    url: "ws://127.0.0.1:8443",
    auth: "user:secret",
    mode: "allow",
    allow: ["example.com"],
    fallback: "local",
  },
});

Verify it's working

  • The client log prints canvas-bridge: connected to <host>:<port> on a successful connection (run with --enable-logging=stderr --v=1 to see it).
  • Load a page that hashes a canvas/WebGL surface — with the bridge connected the hashes match the bridge host's GPU, not your automation host's. Quick check: run the same canvas.toDataURL() with and without the bridge; the results differ.
  • If the bridge is unreachable, Clearcote logs a warning and falls back to local rendering — a misconfigured bridge degrades gracefully, it never breaks the page.

Caveats & limits

  • Canvas identity = the bridge host, not the seed. Every profile sharing one bridge host shares that host's canvas/WebGL hash, so they are linkable by canvas hash. For many unlinkable identities, run one bridge host (GPU) per identity group.
  • Latency. Each readback is a blocking network round-trip (5s timeout, then local fallback). Keep the bridge host on the same LAN/datacenter; avoid the bridge for latency-sensitive, canvas-heavy pages.
  • Procedural WebGL textures are bridged. Image, 2D-canvas, video, ImageBitmap and 3D texture sources fall back to local rendering for that canvas, so they remain correct but un-bridged.
  • --no-sandbox is required on the client, and transport is plaintext — always tunnel; never expose the bridge port publicly.
Experimental and shipped in v0.1.0-pre.12. For the canonical reference (with the full troubleshooting table), see the canvas-bridge guide on GitHub. Most setups don't need the bridge — see Fingerprint flags for the standard engine-level controls.