Build1 distinct publisher3 min readUpdated
A dev.to writeup traces stubborn 403s to the TLS handshake: JA4 sorts the client before a header is read, and a User-Agent that disagrees with the handshake is worse than no lie at all.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
Order is the detail that catches people who have already read one post about fingerprinting. Two clients can advertise an identical set of cipher suites and still hash to different identifiers, because the sequence is part of the input [5]. Enabling the algorithms Chrome enables is not the same as offering them in Chrome's order with Chrome's extensions in Chrome's positions [5]. Browsers also pad the hello with GREASE, meaningless values inserted at randomized positions, so an imitation that omits them or places them predictably is identifiable by being too tidy [6].
Nobody reconstructs that by hand. The dev.to author's answer is a library linked against a patched TLS stack that ships presets copied from specific browser builds, in Python `curl_cffi`, where one `impersonate` value sets cipher order, extension order, curves, ALPN and GREASE padding [7]. The interesting part is the failure mode on the way there. If the handshake is Chrome 120's and the User-Agent says Chrome 131, the defense is looking at two identities that cannot both be true, and the author's read is that the contradiction is worse than an honest Python fingerprint, because a known automation library is merely automation while a mismatch is an attempt [8]. A half-done migration, new TLS layer with the old header block still in the config, moves your client into the worse bucket.
That reframes the spending. Rewriting headers, rotating proxies and adding delay all act at or after the HTTP request, and none of them touches the bytes the classifier already hashed [1]. The same 403 arriving slower, which is what the author kept getting, is the expected result rather than a mystery [12]. What passes is a conjunction: the handshake has to resolve to a browser build in the table, the version you claim elsewhere has to match the preset, and the address has to survive its own scrutiny, because a perfect Chrome handshake from a data-center IP is still coming from a data-center IP [2].
There is a measurement problem sitting underneath all of it. A challenge page usually arrives wearing a 200 [13]. Any dashboard that counts non-2xx responses will report a healthy fetcher while the parser quietly digests interstitials, which is how a team spends a quarter on proxy pools without ever seeing that the block rate never moved.
Two ongoing costs follow. Presets age, and a preset reproducing a three-year-old Chrome build is anomalous once nearly nobody runs that build, so tracking the impersonation target is maintenance rather than setup [9]. And the handshake runs none of the JavaScript probes that come later when a challenge is issued [11]. Getting the first packet right stops you being sorted into automation before you speak; it does not buy anything after that.
Ranked by verification strength, evidence, and original report placement.
Before any HTTP request, the client opens a TLS connection whose first message is the ClientHello, which is plaintext and announces supported cipher suites in preferred order, understood extensions in order, accepted elliptic curves, signature algorithms, and the ALPN protocol list.
The application code does not choose those values; the TLS library beneath the HTTP library does. Python's ssl module makes one set of choices, Go's crypto/tls another, Chrome's BoringSSL a third.
Those choices are stable per stack and different across stacks, which is exactly the property a fingerprint needs.
The defense hashes the ordered set into a short identifier, JA3 in the older scheme and JA4 in the newer and more structured one, then looks it up in a table. Real Chrome and Firefox builds are in the table; python-requests is also in the table, filed under automation.
Order is part of the signal: two clients supporting an identical set of ciphers fingerprint differently if they list them in a different sequence, so the ciphers must be offered in Chrome's order with Chrome's extensions in Chrome's positions.
GREASE entries are meaningless values browsers insert at randomized positions to keep the TLS ecosystem tolerant of unknown extensions; an imitation that omits them or places them predictably stands out by being too tidy.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Coherent mechanism, single unverified source
The technical chain — plaintext ClientHello, library-chosen parameters, JA3/JA4 hashing into a lookup table, order and GREASE as signal — is described precisely and illustrated with an abbreviated side-by-side plus a runnable curl_cffi snippet, which supports the mechanism claims. But the cluster has exactly one source, a personal dev.to post with no links to the JA4 specification, no defense-side documentation, and no measurement behind its behavioral assertions (mismatch penalty, preset aging, 'usually a 200'), so several claims land at insufficient.
One practitioner usage report
The only adoption signal in the supplied material is the author's own disclosed use of curl_cffi's impersonate preset. There are no download figures, no organizational deployments, no named targets, and no reported success rates, so adoption is real but minimal and anecdotal.
Mildly overstated behavioral claims
The post is unusually self-limiting for the genre — it states plainly that a correct handshake is not sufficient, that data-center IPs still draw suspicion, and that the handshake touches none of the later JavaScript probes — which keeps the gap small. The residual overstatement comes from asserting defense behavior as settled fact: that a handshake/User-Agent contradiction is strictly worse than honest automation, that stale presets get flagged, and that challenge pages 'usually' return 200, none of which is measured or externally sourced.
Low disclosed commercial incentive
The single source is an individual-authored post on a developer-community platform. It recommends a third-party open-source library rather than a product the author is shown to own or sell, and the supplied material discloses no vendor sponsorship, affiliation or paywall. The residual incentive is the ordinary attention return on a confident how-I-fixed-it narrative, which plausibly explains the unhedged behavioral assertions.
Moderate on mechanism, weak on effects
Confidence is moderate because the structural claims about the ClientHello and fingerprint construction are specific, mutually consistent, and match how the described tooling works, and because the author's failed-remedy account is a plain first-person report. It is held down by the absence of any second publisher, any measurement of outcomes, and any defense-side confirmation of the behavioral and frequency claims.
Follow any of these and your For You feed starts watching them — no settings page required.
build
The exit ping that never left: fetch in beforeunload loses to the browser's unload policy1 distinct publisher
build
Three ways to ask who embedded your iframe, and only one the host cannot switch off1 distinct publisher
build
The eBay scraper defect that bills your customer: sponsored cards in organic markup1 distinct publisher
build
height:auto is animatable now, so your max-height ceiling is a bug you can delete1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 22, 2026