The check that happens before your request
When you set a tool to send a custom User-Agent and the WAF still blocks it instantly, the block did not come from the header. It came from the TLS handshake, which happened before a single byte of HTTP was written.
To open a TLS connection, a client sends a ClientHello describing what it supports: which cipher suites, which elliptic curves, which extensions, in which order, with which values. That set is chosen by the TLS library and its version, and different libraries make different choices. The result is a high-entropy signature of the client software.
Hashing the stable parts of that structure gives a fingerprint — JA3 traditionally, JA4 more recently. The server computes one hash and can compare it against a known list. A stock script library's fingerprint is as recognisable as a browser's, and it does not change when you edit a header.
Why the User-Agent does nothing
This is the part that surprises people. The User-Agent is inside the encrypted HTTP stream, at the application layer. The TLS fingerprint is derived from the handshake, one layer below and one step earlier. Editing the header is editing the wrong layer entirely. A client claiming to be Chrome while presenting a scripting language's cipher ordering is not a browser with a spoofed name — it is an obviously inconsistent client, which is more suspicious than an honest one.
Detection does not stop at TLS
Anti-bot systems correlated multiple layers a long time ago, and the layers have to agree with each other:
- TLS — the ClientHello shape.
- HTTP/2 — the SETTINGS frame values, window sizes and the order of pseudo-headers. Real browsers are consistent here too, and a mismatched H2 fingerprint alongside a browser-like TLS fingerprint is a contradiction.
- Headers — header order and casing follow the client that sent them.
- JavaScript — the browser environment as the page's own scripts see it: automation markers, plugin lists, WebGL renderer strings, canvas output, font enumeration.
A WAF that fingerprints any one of these sees a puzzle. If four layers say Chrome and the fifth says a headless script, the fifth layer is the tell.
Challenges are the second gate
Fingerprint checks decide whether you get a normal response, a challenge, or a block. The challenge — a JavaScript proof-of-work, a captcha, a cookie-setting script — is a second, independent gate. Passing the fingerprint check without being able to execute the challenge gets you a page that loads and a session that never becomes trusted. That is why solving challenges requires running the script, not reading it: the cookie the script sets is the proof.
What actually changes the outcome
The only durable approach is a profile that is coherent at every layer at once. That means reproducing a real browser's TLS ClientHello — cipher order, curves, extension order, GREASE values, and the post-quantum key exchange modern Chrome offers — and matching its HTTP/2 settings, and presenting a JavaScript environment consistent with the same browser and operating system.
Hugin's Mirage does this inside the proxy's own connector rather than delegating it to a separate browser, so the proxy, the scanner, the crawler and the browser automation all present the same coherent profile. When each layer agrees, the target has to make its decision on behaviour rather than on shape — which is where security testing is supposed to happen.
It is worth being clear about the purpose: this is for authorised testing and for reaching applications that block legitimate tooling, not for evading a target you have no permission to test.