Skip to main content

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:

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)

Source distribution for clienthello-probe 0.1.0
File Size Uploaded Provenance
clienthello_probe-0.1.0.tar.gz 9.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for clienthello-probe 0.1.0
File Interpreter ABI Platform
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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page