Anti-bot protection is usually described as a wall. It is closer to a stack of independent checks, each reading a different part of the connection, each costing the operator something different to run and the automated client something different to defeat.
Meta's stack makes a good case study because an unusual amount of it is documented in public, in research papers and vendor specifications. What follows is assembled from those sources rather than from probing the platform, and contains no bypass instructions. The useful part is knowing which layers are cheap theatre and which ones decide the outcome.
The layers
signal cost to fake cost to run
---------------------------------------------------------------
1 TLS handshake (JA3/JA4) low trivial
2 HTTP/2 connection preface low trivial
3 IP and ASN reputation medium cheap
4 Browser surface consistency medium cheap
5 Client attestation (mobile) very high cheap
6 Behavioral telemetry high moderate
7 Account graph position impractical expensive
Most anti-detection tooling addresses layers 1 through 4. Accounts die at layers 5 through 7. The ordering follows one rule: a signal is worth what it costs the attacker to control, not what it costs the defender to collect.
Layers 1 and 2: the connection describes the client
Before the first byte of a request exists, the TLS ClientHello has described the stack that produced it. Cipher list and order, extensions, supported groups, signature algorithms, ALPN. None of it is set by application code, which is why libraries differ from each other and why the fingerprint survives IP rotation and header spoofing. JA4, from FoxIO, encodes that as a partly readable string rather than JA3's opaque hash.
t13d1516h2_8daaf6152771_b186095e22b6
││ ││ │ │ │ └── extension + signature-algorithm hash
││ ││ │ │ └── cipher suite hash
││ ││ │ └── ALPN (h2)
││ ││ └── extension count (16)
││ │└── cipher count (15)
││ └── SNI present (d = domain, i = IP or absent)
│└── TLS version (1.3)
└── transport (t = TCP, q = QUIC)
HTTP/2 leaks the same way. An Akamai white paper from Black Hat EU 2017 identified four sources: the SETTINGS frame contents and ordering, the initial WINDOW_UPDATE increment, unsolicited PRIORITY frames, and the order of the request pseudo-headers. Those separate Chrome from Firefox from Go's standard library from curl cleanly, because each sits hardcoded in the implementation rather than exposed as configuration.
Both layers are cheap to normalize once known, so neither works as a standalone block. They filter the large volume of traffic from stock HTTP clients and contribute a feature to a larger model for everything else.
Layer 3: the address is a prior
IP and ASN reputation carries more weight in proxy marketing than it earns. What an address supplies is a prior probability. A datacenter ASN carrying 400 logins in an hour is a strong prior, a residential ASN carrying one is weak, and neither decides anything alone, because addresses are shared and a real user on mobile data changes IP several times a day.
Address history matters once it is joined to an account. One account appearing from twelve ASNs on three continents in an afternoon is an account-consistency signal that happens to arrive through addresses.
Layer 4: the tell is contradiction
Browser fingerprinting is commonly misread as a uniqueness problem where the goal is to look unremarkable. The stronger signal is internal contradiction. FP-Scanner (Vastel et al., USENIX Security 2018) showed that countermeasures defeat themselves, because altering attributes produces combinations that cannot occur naturally. Their consistency battery reached 1.0 accuracy detecting spoofing tooling against 0.45 for FingerprintJS2, and recovered the real browser family every time.
// Capture this before page script can shadow it. Calling
// navigator.hasOwnProperty() instead lets one assignment blind the check.
const hop = Object.prototype.hasOwnProperty;
const probe = {
uaClaim: navigator.userAgent,
platform: navigator.platform,
evalLength: eval.toString().length, // 33 Chromium, 37 Firefox/Safari
errorShape: (() => { try { null.f() } catch (e) { return e.constructor.name } })(),
touch: 'ontouchstart' in window,
cores: navigator.hardwareConcurrency,
navOwnProps: Object.getOwnPropertyNames(navigator)
.filter(k => hop.call(navigator, k)), // should be empty
};
A user agent claiming Firefox beside an eval length of 33 is a contradiction. So are touch events on a machine reporting a desktop OS, a Mesa renderer next to a MacIntel platform string, and own properties on the navigator instance, which the WebIDL spec places on the prototype.
Two limits are worth stating. The paper's rule treating ANGLE as a Windows indicator has expired, since ANGLE is now Chrome's default graphics layer on Linux, macOS and Android, so that check fires on ordinary users today. And the own-property test catches naive spoofing only, because prototype-level patching never touches the instance, which is where current automation frameworks work. Cost is not the constraint on running any of it: stopping at the first contradiction resolved 83 percent of fingerprints in 0.21 milliseconds.
Layer 5: attestation closes the mobile surface
A browser is a hostile execution environment from the site's perspective, since everything the page measures runs on the client and can be lied about. Native apps escape that through hardware. Google's Play Integrity API returns a signed verdict, and on Android 13 and above MEETS_DEVICE_INTEGRITY carries hardware-backed proof that the bootloader is locked and the running OS is a certified manufacturer image. An empty verdict indicates rooting, API hooking, or a failing emulator, and a separate PLAY_RECOGNIZED field confirms the package and certificate match what Play distributes. Apple's App Attest does the equivalent through the Secure Enclave.
The consequence runs against intuition. A large app's mobile API is usually the harder target of the two, because a signed attestation from a locked device is a claim that infrastructure cannot manufacture. Web endpoints stay comparatively open because browsers cannot make that claim either, which forces the defense there to be statistical rather than cryptographic.
Layers 6 and 7: the account graph
Meta published Deep Entity Classification at USENIX Security 2021. It describes Facebook rather than Instagram and is five years old, so read it as a window into how the company approaches this class of problem rather than a current specification.
The premise is that direct account features are attacker-controlled and therefore weak. Account age, connection count, posting frequency and profile completeness are trivially set by whoever runs the operation. So the system classifies on something else: over 20,000 deep features per account, aggregated from the properties of direct and indirect graph neighbors across multiple hops. In production the authors reported precision above 0.97, recall of 50 percent at 0.95 precision against 22 percent for a single-stage version, and hundreds of millions of accounts actioned over two years on 0.7 percent of global CPU.
The finding worth sitting with concerns evasion. Across the evaluation window the authors observed no adversarial adaptation to the deep features, unlike their experience with earlier systems. The reason is economic. Moving an account's aggregated neighbor statistics means controlling its neighbors and their neighbors, and doing that at volume produces the coordination side effects cheaper systems already catch. This is why enforcement arrives in batches rather than one account at a time. Whatever grouped those accounts was structural, and no header or proxy pool addresses a signal that is not about where the requests came from.
What the law allows
In January 2024, Judge Edward Chen of the Northern District of California ruled in Meta Platforms v. Bright Data (23-cv-00077-EMC) that Meta's terms did not prohibit scraping publicly available data while logged off, reasoning that Bright Data was not an active user in that state. Chen also declined to enforce a survival clause banning scraping in perpetuity after account termination, writing that letting companies decide who may collect and use data risks creating information monopolies. Meta was later reported to have abandoned the case.
The result is real and narrower than the coverage suggested. It is a contract ruling on one set of terms in one district, says nothing about logged-in access, does not address the CFAA, and does not touch GDPR, which is indifferent to contract position and very much concerned that profile data is personal data. The defensible zone is public, logged out, no account, no circumvention of a technical access control, and a lawful basis for anything involving identifiable people in Europe.
Designing collection around this
Start with the official API even when it looks insufficient, because it supplies a published budget rather than a guessed one. Meta's platform limit for app tokens is 200 calls per hour times user count, and Instagram's non-messaging endpoints run on 4800 times impressions per 24 hours. Usage returns in headers as you go, though the two endpoint families use different ones: flat X-App-Usage for app-token Graph calls, and X-Business-Use-Case-Usage keyed by business ID for Instagram Platform and Marketing endpoints. Handle only the first and the backoff never fires on exactly the endpoints that needed it. The unofficial path supplies no such number, so the limit gets learned by exceeding it.
For public logged-out collection, spend the engineering on coherence and pacing rather than volume. One internally consistent client moving at a plausible rate outlasts fifty that are individually perfect at the TLS layer and collectively obvious. Session stickiness matters more than pool size, since a pool rotating addresses mid-session manufactures the inconsistency layer 3 watches for.
Anything requiring authenticated accounts at scale belongs in the business risk column rather than the engineering one. It works for a while, which is the trap. The failure mode is losing accounts in batches with no diagnostic signal explaining why. That caps what can be collected, and the compensation is that the cap is stable, which is something to build on in a way a success rate that resets every quarter is not.
Top comments (0)