Chrome 155.0.8059.40 on macOS, and nothing else. Every other browser and system pair is refused with NotMeasured.
Tested against local captures and servers, not against live services. No guarantee against any detection service.
Limits#
State these plainly to yourself before you build on any of this.
-
macOS and Chrome 155 only. Exactly one browser and system pair is measured: Chrome 155.0.8059.40 on macOS. Every other pair is refused with
NotMeasured. When Chrome updates, the profile does not follow until you re-measure. -
Known gaps between Chrome’s handshake and ours. The pinned BoringSSL and wreq cannot send three things Chrome 155 sends:
TrustAnchorsExtension: thetrust_anchorsextension (0xCA34). It needs a newerbtlsandwreqthan the exact pins. Our JA4 therefore differs from Chrome’s in the extension count and thechash.MlDsaSignatureAlgorithms: the ML-DSA signature schemes (0x0904to0x0906) are missing from our signature algorithms, which changes thechash too.- A GREASE signature algorithm: Chrome writes one first, and the vendored
BoringSSL writes none. This one is not a
KnownGapvariant. JA3 and JA4 strip GREASERandom reserved values a client adds to its handshake so servers do not depend on a fixed list (RFC 8701)., so only the peetprintA JA3-like format that also records signature algorithms. shows it. A test pins the difference and fails when a newer BoringSSL closes it.
The first two are recorded in the measured file’s
provenance.known_gaps; the tests assert that the difference between Chrome and our client is exactly these, and fail when one disappears.HttpSessionandMitmProxysend the handshake of this client, so the gaps apply to them. Behind theMitmProxythe origin sees our handshake, gaps included, and not Chrome’s own, whichBrowserScraperleaves with unchanged. That is the price of one wire identity in both modes of aStealthSession. -
No HTTP/3. wreq has none, and Chrome behind the proxy has QUIC switched off.
-
WebSocket passes the
MitmProxyaswss://andws://(the latter in plain text, so with no hello to impersonate); every other upgrade is refused. It needs the proxy’s credentials in Chrome first:configure_launchloads the priming page at the launch, but a browser context created later has to loadmitm.prime_url()itself (see the priming page).HttpSessionopens no WebSocket. -
The first request to an HTTP/1.1-only origin carries an HTTP/2
priorityheader, inHttpSessionand inMitmProxy, because the protocol is known only after the connection opens. Later requests to that origin do not carry it. -
Headless, one system. The tests that drive Chrome through
MitmProxy,HttpSession::from_pageandStealthSessionlaunch it headless on macOS with Chrome 155; none drives them headed. The measured tables were captured from a headed Chrome, and headless and headed Chrome differ. -
HttpSessionfollows redirects where they point, a private address included. Filter the URLs before the session sees them if they are not yours. -
Tested against local captures and servers, not against live services. The wire layer is tested against committed captures and, in tests marked
#[ignore], against a local Chrome. TheBrowserScraperflows are tested with a scripted browser and with ignored tests that drive a local Chrome.HttpSession,MitmProxyandStealthSessionare tested against loopback TLS sites and a challenge page written for the tests, with a scripted browser, and in ignored tests with the local Chrome. Nothing here was checked against a live bot-protection service. The identity file’s permissions check is Unix only. -
No guarantee against any detection service. A matching handshake is one signal among many: the IP address, the behaviour, the account and the page’s own checks all count. Nothing here promises to pass a given service, and services change.
-
Scrape within the rules. Follow each site’s terms of service and
robotsrules, respect rate limits, and obey the law where you and the site are. A matching fingerprint does not make automation permitted. The library does not decide that for you.
Troubleshooting#
`NotMeasured { kind, os }`
Likely cause. The browser and system pair has no capture.
Fix. Run the capture tools on that system, or use the measured one.
`VersionMismatch { browser, measured }`
Likely cause. The identity’s user agent claims another Chrome major than the measured profile.
Fix. Build the identity with Fingerprint::randomized_with_major(os, seed, measured_major), or re-measure.
`UnsupportedExtension`, `UnsupportedSigalg`, `UnsupportedSetting`
Likely cause. The input asks for something BoringSSL or wreq cannot send.
Fix. Remove it. The error names the id.
The build fails while compiling BoringSSL
Likely cause. cmake or a C/C++ toolchain is missing.
Fix. Install them, or drop the mimic crate.
`IdentityFileError::Permissions`
Likely cause. Group or others can read the identity file.
Fix. chmod 600 it, or delete it and make a new one.
`ScrapeError::CoolingDown`
Likely cause. The site answered with a block or rate limit and is being left alone.
Fix. Wait until the time in the error (seconds since the Unix epoch), or give the scraper another egressThe proxy a request leaves through..
`ScrapeError::Unsolved`
Likely cause. A challenge was still there after the attempts allowed.
Fix. Raise with_max_challenge_attempts, add egresses, or accept that this page needs a person.
`ScrapeError::NoEgressAvailable`
Likely cause. A rotation was asked for and the pool has nothing else in rotation.
Fix. Add proxies with with_geo_proxies.
`HttpError::VersionMismatch`, `StealthError::Impersonate`
Likely cause. The page, the identity or the installed Chrome is another major than the measured one.
Fix. Update or downgrade Chrome to the measured major, or run the capture tools again.
`HttpError::Impersonate` for a page
Likely cause. The page announces itself as HeadlessChrome, is not Chrome, or runs on a system that was not measured.
Fix. Give the browser a fingerprint or run it headed; use the measured system.
`HttpError::ReservedHeader`
Likely cause. A request set a header the profile owns (User-Agent, Sec-CH-UA*, Accept-Language, Accept-Encoding, Cookie, priority).
Fix. Remove it. Change the identity instead.
`HttpError::RedirectRefused`, `TooManyRedirects`
Likely cause. A hop led somewhere the session refuses, or past the limit.
Fix. Request the final URL, or raise max_redirects.
`HttpError::Identity(EgressMismatch)`
Likely cause. The identity or the page belongs to another egress than the one passed.
Fix. Pass the egress the cookies were earned through.
Chrome behind `MitmProxy` shows a `502` page
Likely cause. The proxy refused the request or the upstream failed (an untrusted certificate, a non-WebSocket upgrade, a version mismatch).
Fix. Check mitm.stats() and the log; trust_upstream_root for a private site.
Verified against commit 4184e74 and the re-exports added after it, by reading
crates/codec/src/**, crates/scrape/src/**, crates/mimic/src/**, crates/scrape/examples/capture_*.rs, crates/mimic/examples/capture_h2.rs,
crates/codec/src/measured/chrome-155-macos.json and the tests that pin the behaviour
described here. The code samples were checked against the signatures in those
files; they were not compiled or run for this check. The real-Chrome tests named
here are #[ignore]d and were not run for this check.