// scrapfly engineering
~/scrapfly $ cat blog/*.md
We take anti-bot systems apart at the byte.
Engineering deep dives, postmortems, and notes from the team behind scrapfly.io. Anti-bot teardowns, browser internals, distributed scraping at scale.
A note on how these are written: the posts here are drafted with AI. That lets our engineers put research and findings out quickly and spend their effort on the technical substance instead of the prose. The mechanisms, the numbers, and the code are ours.
Your Browser's Hyphenation Dictionary Names the OS It Runs On
CSS hyphens:auto uses a per-language dictionary, and macOS and Windows/Linux Chrome ship disjoint dictionary sets from two different engines. Which languages a browser will hyphenate, and where it breaks them, classifies the OS with one DOM read. Why closing it for a macOS profile means reverse-engineering Apple's CFBurstTrie format and reconstructing its hyphenation bit-for-bit.
Your Browser Does Math Differently on Every OS, and Anti-Bot Systems Read the Bits
Math.tanh, every CSS trig function, and the Web Audio compressor all route through the host libm, so the rounding of a cosine betrays the OS a browser actually runs on. Where the leak lives across V8, Blink, and Web Audio, and what bit-for-bit reproduction of Apple's math library takes to close it.
The Math Behind the Audio Fingerprint
The audio fingerprint stopped separating browsers and now separates CPUs: x86 and Apple Silicon build the oscillator wavetable with different FFTs. Why per-session noise cannot hide a 1-to-22-ULP gap, and what reproducing Apple's vDSP FFT on x86 down to a double-precision base case actually involves.
WebAssembly Runs Identically Everywhere, Except Where It Leaks Your CPU
WebAssembly is deterministic except for NaN bit patterns and relaxed SIMD, and both leak whether the CPU underneath is ARM or x86. How a browser claiming Apple Silicon on an x86 server gets caught by a 30-byte module, and how to emit ARM's bits across every V8 compiler tier.
CDP over CBOR, msgpack and Snappy: three formats, one bug
How a single CDP message travels through three different serialization formats between Scrapium (our stealth-patched Chromium build) and our scraping infrastructure, what each format is doing for us, the UTF-8 vs UTF-16 trap that lives at the CBOR layer, and a production case our monitoring caught that taught us our two implementations did not agree on what a 'string' was.