Skip to main content
You run bot protection on your site and want to see what it records when a browser presents a given machine. This walkthrough submits a signup form once for each of four persona and seed pairs and once in host mode, then prints what the backend received. Run it against sites you operate or are engaged to test.

What Apostate changes

Each launch presents one composed machine of a persona, the platform you choose. The User-Agent and Sec-CH-UA-* headers, navigator, navigator.userAgentData, the screen and the WebGL renderer all describe the same Windows, macOS or Linux machine, so the checks a backend runs between headers and JavaScript agree. A seed selects the machine, and a run with the same seed presents the same values. Host mode (fingerprint="host") presents the host’s own values from the same browser build, which gives you a baseline. Personas lists every value a persona sets.

Set up

apostate fonts install windows is for Linux and macOS hosts (Fonts). The folder holds two files:
  • signup_site.py serves a signup page on 127.0.0.1. The page’s own script collects a probe and posts it with the form. The server appends the request headers and the probe to out/visits.jsonl.
  • compare.py submits the form once per run and prints what the server recorded.

The probe

The page reads values that bot protection scripts commonly read:
The server stores the probe next to these request headers:

The runs

compare.py launches one browser per run, fills in the form and waits for the server’s answer:
For each record, it runs the checks a backend can make on a single request:

Run it

This output is from Apostate 0.4.3 on an Apple silicon Mac with 14 cores.
  • Every persona passes every check. Its headers and its JavaScript values name the same platform.
  • Seeds 42 and 7 are two different Windows machines, with different memory, screens and GPUs.
  • The host row is the Mac itself. Headless host mode reports Chrome’s headless 800x600 screen with no taskbar, and the last check flags it. A persona reports its own screen in headless mode too.
  • Core and memory counts depend on the host as well as the seed. The host cap explains how.
Each line of out/visits.jsonl is one signup as your backend would log it. The windows-42 record:
architecture is arm because a Windows or Linux persona reports the host’s CPU architecture, and this host is an Apple silicon Mac. No real Windows machine pairs arm with a desktop Intel GPU, and some detectors flag the pair. To test a Windows persona without it, run the script on an x86-64 host. ARM hosts covers the gap.

Test your staging site

Pass your own page with --url. The script opens it once per run with ?run=<name> added to the URL, waits until the network is idle so that your detection script has run, and prints the time in UTC and what the browser presented:
Find each visit in your server logs and in your detection vendor’s dashboard by its run parameter or its time. Read the score, the bot flag and the rule that fired for each visit, and compare the persona runs with the host run. Runs start 10 to 15 seconds apart. --delay changes the 10 seconds. --url only loads the page. To submit your real form, copy sign_up() and change its selectors to your form’s, with a test address your backend can clean up.
Cloudflare Turnstile’s test sitekey 1x00000000000000000000AA passes every browser, and 2x00000000000000000000AB fails every browser. To see how Turnstile treats a persona, use your real sitekey on a staging hostname that you added to the widget.

Points for this job

  • Seeds. Fixed seeds make each run repeatable, so a change in your rules shows as a different verdict for the same machine. To sample new machines instead, leave out fingerprint, and each launch draws one. Seeds and identity covers both.
  • Proxies. These runs come from 127.0.0.1 or from your own network. Bot protection scores the IP address too (Detection). To include it in a test, add proxy to a run and remove REGION from its launch, so the package sets locale and timezone from the proxy’s exit. Proxies covers proxy URLs.
  • Behaviour. page.fill() and page.click() send input with no mouse path and no typing rhythm. A product that scores behaviour sees that. Detection covers what is up to your script.
  • Headless or headed. The runs are headless, the packages’ default. On a Linux server, headless=False needs Xvfb installed (Linux servers).
  • Public test pages. Verify lists public pages that report what a detector sees.
  • Measured results. Latest results has the dated results of Apostate’s test suite per host, public detector pages included. Measure with FingerprintJS Pro reads that detector’s suspect score and flags for a persona.