Target-bounded and time-bounded puzzles
There are two common designs. A target-bounded puzzle sets a difficulty and lets the browser work until it finds a nonce that meets it. The open-source Anubis gatekeeper works this way: a slow device takes longer, but every visitor eventually gets through, and a crawler pays the CPU cost once per session.
A time-bounded puzzle sets a time budget instead. One widely deployed challenge widget, observed in October 2026, starts min(32, max(1, ceil(hardwareConcurrency x 0.75))) dedicated workers, gives each its own nonce range and a fixed budget, and when time runs out reports each worker's best digest and how many digests it computed. The answer is then a proof and a benchmark at once: how many hashes this browser did in a known time, on as many threads as it claimed to have.
What the hash rate reveals
The number of workers comes from the session's own claim. A session that reports 16 logical cores starts 12 workers; one that reports 4 starts 3. If those cores are really free, the total grows with the worker count. If they are not, the workers compete for the same cores and the total stays flat while the count grows. The same widget also times four WebAssembly arithmetic kernels in a separate worker, which gives a single-thread speed for comparison: total hashes divided by workers should match what that speed predicts.
Neither check needs a reference database; both compare the session with itself, which is the same idea as cross-API coherence checking. Typical contradictions:
- Dense hosts. A server running dozens of browsers presents each with a desktop core count, but each gets a fraction of the CPU.
- Containers with CPU limits. A container limited to two CPUs may still report the host's 64 cores, or a persona's 8.
- Busy virtual machines. CPU taken by neighbouring guests shows up as uneven counts between workers of one proof.
- Throttled background tabs. Hidden pages run timers and workers slower; the widget records visibility first.
What follows for automation
The claimed navigator.hardwareConcurrency and the CPU a session actually gets have to be chosen together. A browser on a shared host should claim a core count the host can actually give it, and the number of browsers per machine becomes a fingerprint decision as well as a cost one. The budget is measured with performance.now() inside the worker, so the clock also has to behave like the claimed browser's; see clock coherence detection.
Most fingerprint values are things the browser says. A proof of work is something the browser does, and the result is bounded by physics, so no JavaScript patch can report a faster hash rate than the machine delivers. A managed web data API handles this by matching the claimed hardware to the CPU each browser really gets, rather than by changing what the page reads.
