The layer below JavaScript: TLS ClientHello and HTTP/2 fingerprinting, impersonating clients that emit a browser-shaped handshake, and the passive tooling that reads them. This is the half of a fingerprint no page script can change, and it is where a browser-shaped page surface most often contradicts a tool-shaped connection.
The layer below JavaScript: TLS ClientHello and HTTP/2 fingerprinting, impersonating clients, and the passive tooling that reads them.
Why ACTIVE: pushed_at = 2026-08-02 — 12 days, within the 90-day ACTIVE threshold.
Field
Value at this printing
pushed_at
2026-08-02
archived
false
latest_release
v1.8.2 · 2026-08-02
license.spdx_id
BSD-3-Clause
stargazers_count
2500
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/refraction-networking/utls
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why ACTIVE: pushed_at = 2026-08-04 — 10 days, within the 90-day ACTIVE threshold.
Field
Value at this printing
pushed_at
2026-08-04
archived
false
latest_release
—
license.spdx_id
MIT
stargazers_count
74
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/refraction-networking/uquic
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why ACTIVE: pushed_at = 2026-08-08 — 6 days, within the 90-day ACTIVE threshold.
Field
Value at this printing
pushed_at
2026-08-08
archived
false
latest_release
v2.1.0 · 2026-08-08
license.spdx_id
MIT
stargazers_count
2700
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/lexiforest/curl-impersonate
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why NRND: pushed_at = 2024-07-18 — 757 days, inside the 365–1095 day NRND band.
Field
Value at this printing
pushed_at
2024-07-18
archived
false
latest_release
—
license.spdx_id
MIT
stargazers_count
6800
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/lwthiker/curl-impersonate
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
The most common single mistake in this whole field: installing the repository with the most stars. Check the push date first — that is all this catalogue is for. — ED.
Why ACTIVE: pushed_at = 2026-08-08 — 6 days, within the 90-day ACTIVE threshold.
Field
Value at this printing
pushed_at
2026-08-08
archived
false
latest_release
—
license.spdx_id
MIT
stargazers_count
6300
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/lexiforest/curl_cffi
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why ACTIVE: pushed_at = 2026-07-02 — 43 days, within the 90-day ACTIVE threshold.
Field
Value at this printing
pushed_at
2026-07-02
archived
false
latest_release
v1.15.1 · 2026-07-02
license.spdx_id
BSD-4-Clause
stargazers_count
1800
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/bogdanfinn/tls-client
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why ACTIVE: pushed_at = 2026-07-07 — 38 days, within the 90-day ACTIVE threshold.
Field
Value at this printing
pushed_at
2026-07-07
archived
false
latest_release
—
license.spdx_id
GPL-3.0
stargazers_count
1500
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/Danny-Dasilva/CycleTLS
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why SEMI: pushed_at = 2026-04-17 — 119 days, inside the 90–365 day SEMI band.
Field
Value at this printing
pushed_at
2026-04-17
archived
false
latest_release
—
license.spdx_id
MIT
stargazers_count
469
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/Noooste/azuretls-client
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why ACTIVE: pushed_at = 2026-07-21 — 24 days, within the 90-day ACTIVE threshold.
Field
Value at this printing
pushed_at
2026-07-21
archived
false
latest_release
—
license.spdx_id
NOASSERTION
stargazers_count
2000
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/FoxIO-LLC/ja4
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Licence is FoxIO's own, not an OSI identifier. GitHub reports NOASSERTION and so does this register — read the file before you vendor it. — ED.
Why ACTIVE: No repository. The link check returned 200 at 2026-08-14T09:12:00Z, and 200–399 is ACTIVE for a non-repository entry.
Field
Value at this printing
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: HEAD https://blog.foxio.io/ja4%2B-network-fingerprinting
No repository, so there is no API field to re-read from a browser. A cross-origin liveness check has to be made server-side; this entry's status comes from the register's own sweep.
Why ARCHIVED: archived = true, set 2025-05-01. A maintainer's own statement outranks every date rule.
Field
Value at this printing
pushed_at
—
archived
true
archived_at
2025-05-01
latest_release
—
license.spdx_id
BSD-3-Clause
stargazers_count
3100
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/salesforce/ja3
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why ACTIVE: No repository. The link check returned 200 at 2026-08-14T09:12:00Z, and 200–399 is ACTIVE for a non-repository entry.
Field
Value at this printing
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: HEAD https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967/
No repository, so there is no API field to re-read from a browser. A cross-origin liveness check has to be made server-side; this entry's status comes from the register's own sweep.
Why ACTIVE: pushed_at = 2026-07-27 — 18 days, within the 90-day ACTIVE threshold.
Field
Value at this printing
pushed_at
2026-07-27
archived
false
latest_release
—
license.spdx_id
GPL-3.0
stargazers_count
445
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/pagpeter/TrackMe
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why ACTIVE: pushed_at = 2026-08-13 — 1 days, within the 90-day ACTIVE threshold.
Field
Value at this printing
pushed_at
2026-08-13
archived
false
latest_release
—
license.spdx_id
Apache-2.0
stargazers_count
1100
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/plabayo/rama
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why DEAD: pushed_at = 2021-08-14 — 1826 days, past the 1095-day outer threshold.
Field
Value at this printing
pushed_at
2021-08-14
archived
false
latest_release
—
license.spdx_id
null (none declared)
stargazers_count
40
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/Xetera/nginx-http2-fingerprint
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why ACTIVE: pushed_at = 2026-08-13 — 1 days, within the 90-day ACTIVE threshold.
Field
Value at this printing
pushed_at
2026-08-13
archived
false
latest_release
—
license.spdx_id
MIT
stargazers_count
44700
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/mitmproxy/mitmproxy
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why DEAD: pushed_at = 2019-07-09 — 2593 days, past the 1095-day outer threshold.
Field
Value at this printing
pushed_at
2019-07-09
archived
false
latest_release
—
license.spdx_id
null (none declared)
stargazers_count
521
http_status
200
fetched_at
2026-08-14T09:12:00Z
rubric_edition
2nd, in force
Endpoint: GET https://api.github.com/repos/p0f/p0f
unauthenticated · 60 req/hr
A live read does not change what is printed above it. The site records that you ran it; what GitHub returned is not kept. The printed state moves only when the register is swept and a new printing is issued. raw JSON
Why the handshake identifies you before a single byte of application data
A TLS ClientHello carries an ordered list of cipher suites, extensions, curves and signature algorithms, and that ordering is a property of the library that composed it rather than of the protocol. Two different TLS stacks negotiating the same connection produce different orderings, and hashing that ordering produces a fingerprint that identifies the client software before any request has been sent.
HTTP/2 adds a second layer of the same kind: the settings frame values, the window size, the priority information and the pseudo-header ordering all differ by implementation. Both were described publicly by the detection side, and both are collected cheaply at the edge, which is why they are among the first things a serious defence looks at.
Impersonation, and the version problem nobody mentions
The tooling here works by emitting a handshake that matches a specific browser build rather than the one the client library would compose naturally. That is a genuine solution and it introduces a genuine new problem: a handshake matching a browser version that no longer exists in the wild is its own kind of signal, and the preset lists in these projects age exactly as fast as browsers ship.
So the field to read on every entry in this section is not only the push date but which impersonation targets the current build carries. A recently pushed library with a two-year-old preset list is presenting a browser that nobody runs.
State marks used in this section
ACTIVE — Shipping. Safe to build on today.
12 here
NRND — Not Recommended for New Designs. Works; do not start here.
1 here
ARCHIVED — The maintainer closed it. Historical reference only.
1 here
DEAD — Unreachable, or untouched past the outer threshold.
2 here
Questions about tls and http fingerprint tooling
Answered from the fields printed above, and from nothing this register has not checked.
What is a JA3 or JA4 fingerprint?
A hash, or in the newer format a structured string, computed from the ordered contents of a TLS ClientHello — cipher suites, extensions, curves and so on. It identifies the client library rather than the user, and it is collected passively at the edge before any application data is exchanged. The newer format is readable by component, which is what makes it debuggable rather than merely comparable.
Can JavaScript change my TLS fingerprint?
No. The handshake is composed by the network stack before any page code exists, which is why a stealth plugin cannot touch it and why a browser-shaped page surface over a Python-shaped handshake is a contradiction that no amount of navigator patching resolves.
Do I need an impersonating HTTP client if I am driving a real browser?
Generally not for the browser's own requests, which already emit a real browser handshake. You do need one for anything you fetch outside the browser, and mixing the two — a real browser for pages and a default HTTP client for the API calls beside them — is a common and avoidable inconsistency.
Donut Browser is filed in section 01 of this same catalogue, under the
same rubric, with the same fields printed against it and no exemption:
an open-source anti-detect browser under AGPL-3.0 whose Wayfern Chromium
fork applies fingerprint overrides at engine level over CDP rather than
by patching page JavaScript, with a local REST API and MCP server on your
own machine, self-hostable sync, zero telemetry, and a pre-launch gate
that refuses to start a profile when the proxy's exit geography
contradicts the profile's fingerprint. It is judged here the same way
everything in this section is judged.