clienthello-probe
See what your HTTP client actually sends. Point any client at a local listener and read back its TLS ClientHello — extension order, cipher list, ALPN, GREASE and JA3 — or, for plain HTTP, its exact header order.
No dependencies. Nothing is proxied anywhere: the tool reads the opening bytes of the connection, prints them, and closes.
Why
A client that lies about its User-Agent still sends the handshake its TLS
library built. That handshake is what filtering systems read first, and it is
produced below the layer most scrapers configure.
Install
pip install clienthello-probe
Use
clienthello-probe # listens on 127.0.0.1:8443
clienthello-probe --once # report one connection, then exit
Then point a client at it:
curl -k https://127.0.0.1:8443/ # TLS: parse the ClientHello
curl http://127.0.0.1:8443/ # plain: show header order
What it shows
Real output from curl 8.7.1:
TLS ClientHello
hello version TLS 1.2
supported_versions TLS 1.3, TLS 1.2, TLS 1.1, TLS 1.0
ALPN h2, http/1.1
extensions (6, in the order sent)
1. supported_versions
2. key_share
3. ec_point_formats
4. supported_groups
5. signature_algorithms
6. alpn
GREASE none sent
Browsers send GREASE. A client that sends none
is distinguishable on that alone.
JA3 MD5 4f2655722e37c542ebeaf1eed48cbbbb
The same probe against a Chrome-impersonating client reports 17 extensions and GREASE present in ciphers, extensions and groups. That difference is the point of the tool.
For plain HTTP, curl sends three headers and no Sec-Fetch-* set. A browser
navigation sends considerably more, in its own fixed order.
As a library
from clienthello_probe import parse_client_hello, NotAClientHello
with open("hello.bin", "rb") as f: # one raw TLS record
hello = parse_client_hello(f.read())
print(hello["ja3"]["md5"])
print(hello["extensions"]) # in the order sent
print(hello["grease"]) # per-field: ciphers, extensions, groups
parse_client_hello raises NotAClientHello on anything that is not a
handshake record carrying a ClientHello, including a hello split across
records — it tells you to read more bytes rather than guessing.
Notes and limits
- JA3 is computed as originally specified: five comma-separated fields with GREASE values removed. It is a fingerprint of the handshake, not an identity.
- The tool never completes a TLS handshake, so the client will report a connection error. That is expected.
- GREASE values are those reserved by RFC 8701.
- Extension names come from the IANA TLS ExtensionType registry. Unnamed extensions print as their number.
Background reading
The measurement studies this tool grew out of:
- Every browser TLS handshake differs; no library's does
- What HTTP clients send before you configure anything
- JA3 fingerprint
Licence
MIT.
Metadata
Release files for clienthello-probe 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| clienthello_probe-0.1.0.tar.gz | 9.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| clienthello_probe-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 18.7 kB
Release files / clienthello_probe-0.1.0.tar.gz
| Download URL | clienthello_probe-0.1.0.tar.gz |
|---|---|
| Size | 9.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eb24145ed04e65577f8906900fac14e90770715d0c758c34ea1c02afdc90fcd9
|
|
BLAKE2b-256 checksum How to use checksums |
7346bfe5d7b6c4bbab622f2650747c4b5f1231fcfe308c655030552c6d36c869
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / clienthello_probe-0.1.0-py3-none-any.whl
| Download URL | clienthello_probe-0.1.0-py3-none-any.whl |
|---|---|
| Size | 9.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
83a094c00253b43de1e47148985d66f7e430ca7d8f325a84b67b13ec1c6ad3db
|
|
BLAKE2b-256 checksum How to use checksums |
baa294952fff7f925f892d90616b4089436b418a15165fde36a089ea63649707
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Security for clienthello-probe 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.