A lightweight Python command-line tool for discovering Encrypted ClientHello (ECH) signals across a list of hostnames. It checks DNS HTTPS (type 65) records for advertised ECH configuration and performs a low-level TLS GREASE ClientHello probe to observe server behavior.
Important: This is an observational scanner, not proof that ordinary client connections use ECH. A DNS ECH configuration indicates that ECH is advertised in DNS. The active probe sends a GREASE ClientHello and does not validate ECH acceptance. A negative or failed probe does not prove that a service lacks ECH support.
- Queries DNS HTTPS/SVCB records (type 65) and detects ECH configuration parameters.
- Follows relevant DNS CNAME and HTTPS AliasMode chains, with depth limits and loop detection.
- Uses configurable DNS resolvers, with UDP queries and TCP fallback for truncated responses.
- Sends a GREASE ECH ClientHello and records observable TLS response signals.
- Supports concurrent scanning of multiple hostnames.
- Accepts optional per-host ports in the input file.
- Exports results to CSV and optionally JSON Lines (JSONL).
- Reports progress and prints domains for which ECH is advertised in DNS.
- Includes timeouts and error details to help interpret incomplete probes.
- Python 3.10 or newer (the code uses modern type annotations such as
X | None). - Network access to the configured DNS resolvers and target hosts.
- Outbound DNS (UDP/TCP port 53) and TLS probe connectivity to the target port (443 by default).
The tool uses Python's standard library; no third-party Python packages are required.
After you push this project to GitHub, clone it with:
git clone https://github.com/pwdex/ECH-Probe.git
cd ECH-ProbeReplace the placeholders with your GitHub username and repository name.
Create a file named domains.txt, with one hostname per line:
# Example targets
example.com
cloudflare.com
www.example.org
example.net:443
Blank lines and lines beginning with # are ignored. Duplicate hostname/port entries are removed. An optional :port suffix can be used for a hostname; otherwise, the default port applies.
Use hostnames, not IP addresses or full URLs. For example, use example.com, not https://example.com/path.
python3 ech_probe.py domains.txtBy default, the tool writes results to ech_results.csv, uses up to 20 concurrent workers, probes TLS port 443, uses a 5-second TLS timeout, and uses a 4-second DNS timeout per resolver.
python3 ech_probe.py domains.txtpython3 ech_probe.py domains.txt --output results.csvpython3 ech_probe.py domains.txt --jobs 30python3 ech_probe.py domains.txt --dns-timeout 3 --timeout 5python3 ech_probe.py domains.txt --dns-servers 1.1.1.1,8.8.8.8Resolver entries may be IPv4 addresses, IPv6 addresses, or resolver hostnames.
python3 ech_probe.py domains.txt --port 8443A port specified directly in domains.txt overrides this default for that hostname.
python3 ech_probe.py domains.txt --output results.csv --jsonl results.jsonlEach line in the JSONL file contains one JSON object for a scanned hostname.
| Option | Default | Description |
|---|---|---|
domains_file |
Required | Input file containing hostnames and optional ports |
-o, --output |
ech_results.csv |
CSV output path |
-j, --jobs |
20 |
Maximum number of concurrent worker threads; must be at least 1 |
-t, --timeout |
5.0 seconds |
TLS probe timeout; must be greater than 0 |
--dns-timeout |
4.0 seconds |
DNS timeout per resolver; must be greater than 0 |
--dns-servers |
1.1.1.1,8.8.8.8 |
Comma-separated DNS resolvers |
--port |
443 |
Default TLS port; must be between 1 and 65535 |
--jsonl |
Not enabled | Optional path for JSON Lines output |
Run python3 ech_probe.py --help to see the command-line help.
| Verdict | Meaning |
|---|---|
positive |
An ECH configuration was found in a relevant DNS HTTPS/SVCB response. This means ECH is advertised in DNS; it does not prove a client can or will complete ECH. |
not_observed |
The DNS lookup completed, but no ECH configuration was observed in the relevant records. This is not proof that the service cannot support ECH. |
inconclusive |
The DNS lookup did not complete sufficiently to make a reliable determination. Review the error fields and retry if appropriate. |
| Field | Meaning |
|---|---|
domain |
Normalized hostname that was scanned |
port |
TLS port used for the active probe |
ech_advertised |
Whether an ECH configuration was observed in DNS |
ech_supported |
Legacy compatibility alias for ech_advertised; prefer ech_advertised |
verdict |
Overall DNS-based verdict |
dns_https |
Whether relevant HTTPS records were observed |
dns_lookup_complete |
Whether the DNS lookup reached a conclusive lookup state |
dns_ech |
Whether an ECH parameter was found in relevant HTTPS records |
dns_ech_b64 |
JSON-encoded list of Base64 ECH configuration values found in DNS |
dns_resolver |
Resolver that supplied the successful DNS response |
dns_record_count |
Number of relevant HTTPS records observed |
active_sh |
Whether a ServerHello was observed during the active probe |
active_hrr_ech_extension |
Whether an ECH extension was observed in a HelloRetryRequest |
active_hrr_ech_extension_len |
Length of the observed HRR ECH extension, if present |
active_ech_acceptance_validated |
Whether ECH acceptance was cryptographically validated; this probe reports false |
active_hrr |
Whether the observed ServerHello was a HelloRetryRequest |
active_address |
Target IP address used for the observed response |
latency_ms |
Elapsed time for the active TLS probe, in milliseconds |
error |
DNS and/or active-probe error details |
dns_raw |
Concise summary of relevant DNS records and alias-chain observations |
active_info |
Summary of observed TLS handshake messages or alerts |
The CSV also contains the corresponding raw ECH configuration data and active-probe metadata. JSONL preserves the complete result objects.
- Normalize input: validates and normalizes each hostname and removes duplicate hostname/port entries.
- Query DNS: requests HTTPS records (DNS type 65), using EDNS(0) and falling back to TCP when a UDP response is truncated.
- Follow aliases: follows relevant CNAME and HTTPS AliasMode chains within configured depth limits.
- Inspect ECH configuration: checks ServiceMode HTTPS parameters for a non-empty ECH parameter.
- Probe TLS: sends a GREASE ECH ClientHello to the target and parses observable TLS records, ServerHello/HelloRetryRequest messages, and alerts.
- Save results: writes a sorted CSV and, if requested, a JSONL file using temporary files and atomic replacement.
- GREASE is not a real ECH handshake. The active probe does not have the target's real ECH configuration, an HPKE context, or the inner-handshake transcript required to validate ECH acceptance.
- A ServerHello is not proof of ECH acceptance. The tool explicitly does not infer acceptance from the presence of an observed extension.
- DNS results are time- and resolver-dependent. Different resolvers, DNS changes, network policies, and caching can affect results.
- A failed probe is inconclusive about ECH support. Firewalls, rate limits, routing problems, server behavior, and timeouts can prevent a response.
- Results describe observations at scan time. They should not be treated as a guarantee about every browser, client, network path, or future connection.
- Scanning has an external network impact. Only scan systems you own or are authorized to assess, and use reasonable concurrency and timeouts.
DNS timeoutor resolver errors: check network access to the selected resolver and try a different resolver with--dns-servers.- TLS timeout or connection refused: confirm that the hostname and port are reachable from your network. A failed active probe does not establish that ECH is unavailable.
not_observed: verify that the hostname is correct and reviewdns_lookup_complete,dns_https, anderrorbefore drawing conclusions.- No output file: check that the destination directory is writable and that the command completed without a file-writing error.
This tool sends DNS queries and TLS ClientHello probes to the specified targets. Use it only for legitimate research, troubleshooting, or assessments you are authorized to perform. Avoid excessive concurrency against services you do not operate.