apijs vmdata surface

Every check whose value reaches hCaptcha's vmdata field, replayed live in this browser by the 20 extracted modules. vmdata is one 24-slot array built by a 68-opcode stack VM hidden inside api.js — plain JavaScript, not WASM — that had to be disassembled and decompiled first. Checks feeding the other payload fields (wdata, imd, topLevel motion data, errors) are deliberately out of scope, and so are the interaction/biometric collectors (mouse, keyboard, touch, clipboard, focus) — this is load, browser and page state only. Copy the JSON here and in another browser, then use Compare to diff them.

browser
L1 / L2 / both
modules0 / 0
collected in
hash ?

The vmdata vector — 24 slots, the thing to compare

This is the payload the VM assembles and ships. Each row is one slot, produced by the module linked on the right. The cards further down are the full transcriptions those values come from — useful for understanding a check, too verbose for diffing two browsers.

Module detail — full transcriptions ()
What am I looking at?

vmdata.js is hCaptcha's api.js bundle — 550 KB of plain minified JavaScript, not a WASM script. Its browser checks live in two layers, and every card below is one extracted module that reproduces the original reads in the original order, with the original guards, coercions and fallbacks.

  • vmdata is one array of 24 slots. Slot i is the VM's global slot v(112+i); slot 23 is a constant baked into the bundle. Most slots are a single probe — devicePixelRatio, screen, visualViewport, the navigation-timing duration, a DOM-shape census, the WebAssembly tamper probe, a cyrb53 hash of window.hcaptcha's own method source, an AI-agent (document.modelContext) probe. Slots 4 and 8 are whole subsystems — the behavioural recorder and the form + perf scanner — serialised wholesale.
  • Membership was decided by measurement, not inference: change one input, collect vmdata again, and see which slot moves. Checks that moved no slot were archived even though they are part of the VM.
  • The VM reaches the browser through only 34 distinct globals, and touches no navigator, canvas, WebGL, AudioContext, fonts, Battery or timezone at all.
  • partial on a card means the module's own header says its transcription is not exact, and why. varies means it was measured to change between runs, so it is excluded from the page hash.

A handful of checks are deliberately described but not fired — the asset-host probe does not issue its request, and the Sentry modules restore every global they wrap. Nothing here leaves the page: no capture is sent anywhere.

Special values that JSON cannot carry are written as «undefined», «NaN», «function name», and so on. Each card's source link opens the exact module that produced it.