Skip to content
Anti-Bot

What Is Call Stack Depth Fingerprinting?

Pim

Pim · Scrappey Research

May 31, 2026 3 min read

What Is Call Stack Depth Fingerprinting? — conceptual illustration
On this page

Stack depth fingerprinting measures the maximum JavaScript recursion depth a browser allows before throwing a RangeError: Maximum call stack size exceeded. In plain terms: it counts how many times a function can call itself before the engine runs out of "stack" space - the limited area where JavaScript tracks calls that are still in progress, like a stack of plates that can only get so tall. That ceiling is decided by the engine (V8, SpiderMonkey, JavaScriptCore), the platform, the CPU architecture, and how much space the test function uses per call. Because the number comes from the real engine and OS - not from the User-Agent (UA), the browser-and-OS label a session claims - it is a cheap way to tell which engine is actually running, and it exposes tools that claim to be one browser while running another underneath.

Quick facts

MeasuresMax recursion depth before RangeError (Maximum call stack size exceeded)
Varies byJS engine, OS, CPU architecture, and the test function frame size
RevealsThe real engine - V8 vs SpiderMonkey vs JavaScriptCore
CatchesFirefox-based tools (Camoufox) presenting a Chrome User-Agent
VariantMeasured separately on main thread, Workers, and WASM

How the limit is measured

The test is short: write a function that bumps a counter and then calls itself, let it recurse until it crashes, and record the counter. That final number is surprisingly specific. V8 (Chrome), SpiderMonkey (Firefox), and JavaScriptCore (Safari) each set aside stack space differently and enforce different ceilings, so the depth falls into a distinct range for each engine. The exact value also shifts with CPU architecture (x86-64 vs ARM64), the operating system (default stack sizes differ), and how much space each call uses - so probes fix the function's shape (the number of arguments and local variables) to keep results comparable across runs.

Because the number is produced by the engine actually executing real instructions, you cannot move it from JavaScript - there is no property to overwrite; the limit is simply where execution genuinely runs out of room.

Why it catches engine spoofing

The most powerful use is catching tools whose engine does not match their claimed browser. Camoufox is Firefox under the hood; if it presents a Chrome User-Agent, its recursion depth lands in the SpiderMonkey range rather than the V8 range - an immediate contradiction. The same goes for any "Chrome" that is really something else, or a JavaScript runtime pretending to be a browser. Measuring the depth in several places - the main thread, a Web Worker (a background JavaScript thread), and WebAssembly (WASM, a lower-level code format browsers can run) - adds even more resolution, because the ratios between those contexts are also engine-specific.

This is one of a family of engine-level probes (alongside Math precision and WASM measurements) that read the real implementation instead of the advertised one. Because the limit is set inside the engine, the value reported by a session is consistent only when the engine actually running matches the browser it identifies as - for example a Chrome-based engine reporting a Chrome User-Agent; the depth is determined at the C++ level and cannot be altered from JavaScript.

Code example

javascript
// Measure max recursion depth (fix the frame shape for comparability)
function maxStackDepth() {
  let depth = 0;
  function recurse() { depth++; recurse(); }
  try { recurse(); } catch (e) { /* RangeError */ }
  return depth;
}

// Indicative ranges (vary by OS/arch/version - the engine clustering is the point):
//   V8 / Chrome           ~ 10k-15k
//   SpiderMonkey / Firefox~ different band
//   JavaScriptCore/Safari ~ different band
//
// A Chrome User-Agent whose depth lands in the SpiderMonkey band
// is a Firefox-based tool (e.g. Camoufox) wearing a Chrome UA.

Related terms

Concept map

Concept map

How Stack Depth Fingerprinting connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

Why does maximum recursion depth differ between browsers?

Because it is set by the JavaScript engine and the operating system, not by the browser brand. V8, SpiderMonkey, and JavaScriptCore reserve stack space and enforce limits differently, and the value also shifts with CPU architecture and how much space each call uses. As a result, the depth clusters by the real engine running - which is exactly what makes it a fingerprint.

Can I spoof my stack depth from JavaScript?

No. The limit is a consequence of how the engine actually executes code - there is no property to overwrite, and no way to make recursion fail at a different point without changing the engine itself. That is precisely why it catches tools whose underlying engine differs from the User-Agent they advertise.

How does stack depth catch Camoufox?

Camoufox is a Firefox fork, so its recursion depth falls in the SpiderMonkey range. If it presents a Chrome User-Agent, that depth contradicts the range V8 (Chrome's engine) would produce, revealing a Firefox engine hiding behind a Chrome identity. Measuring depth separately on the main thread, in Web Workers, and in WASM adds further engine-specific detail.

Last updated: 2026-05-31