Verify an EchelonGraph scan
Found a request claiming to be EchelonGraph in your logs? The HTTP requests our scans send to the hosts they check carry a signed receipt in the X-EchelonGraph-Verify header. The kinds of scan request that carry none, and our other requests that name us, are described further down. Paste the receipt here and we'll tell you whether it was really us, and show what it attests, so you can match it against your log entry. No receipt to hand? Select Try the example to see how it works.
Where do I find this token?
It's an HTTP request header named X-EchelonGraph-Verify. Its value is a link to this page with the receipt in it, and the same receipt, which begins with v1., is sent on its own in X-EchelonGraph-Receipt. Open the link, or paste either value into the box above. A Discovery Radar request looks like this:
X-EchelonGraph-Verify: https://echelongraph.io/verify-scan?token=v1.<signed-payload>.<signature> X-EchelonGraph-Receipt: v1.<signed-payload>.<signature> User-Agent: EchelonGraph-ExposureRadar-Verifier/1.0 (+https://echelongraph.io/surface-scanner; opt-out https://echelongraph.io/responsible-disclosure; [email protected]) From: [email protected]
A Surface Scanner request also names the scan it belongs to:
X-EchelonGraph-Verify: https://echelongraph.io/verify-scan?receipt=v1.<signed-payload>.<signature>&scan=<scan-id> X-EchelonGraph-Receipt: v1.<signed-payload>.<signature> X-EchelonGraph-Surface-Scan: <scan-id> From: [email protected]
Heads-up: a typical default access log (Apache/nginx combined format) records only the client IP and User-Agent — not custom request headers. You'll find the X-EchelonGraph-Verify value in logs that capture full request headers: your application logs, a reverse proxy / load balancer or WAF configured to log headers, or a traffic capture. Paste just the value (everything after X-EchelonGraph-Verify:).
No receipt in your logs? You can still recognise us
Most default logs do capture the User-Agent and source IP. Our scans' User-Agents look like this, and a request that carries a receipt also gives a contact address:
- • Discovery Radars:
User-Agent: EchelonGraph-<Radar>/1.0 (+https://echelongraph.io/surface-scanner; opt-out https://echelongraph.io/responsible-disclosure; [email protected]), or for our crawlerUser-Agent: EchelonGraphBot/1.0 (+https://echelongraph.io/bot) - • Surface Scanner: a Chrome browser User-Agent that ends in
EchelonGraph-SurfaceScanner/1.0 (+https://echelongraph.io/surface-scanner; opt-out https://echelongraph.io/responsible-disclosure) - •
From: [email protected]
A User-Agent on its own can be copied by anyone, which is exactly why the signed receipt exists. A request whose receipt does not verify did not come from us. Nor did an HTTP request to your site that claims to be one of our scans, with a User-Agent in one of the forms above, if it carries no receipt and is not one of the kinds listed under Requests that carry no receipt below. Please report either to [email protected]. Our other software that names us is not a scan and carries no receipt; see Other requests that name us below.
How verification works
A receipt is an HMAC signature made with a key that only our servers hold. A forged receipt will not verify, and a copied one shows facts that will not match the request in your logs. There are two kinds, and they attest different things.
- • A Discovery Radar receipt covers one request. It attests the host or IP address the request was addressed to (a hostname for most radars, an IP address where the radar addressed one directly), the port where the radar records one, the radar's name, and the time the request was made. Three checks that are not radars send this kind of receipt too, and the result shows their names in the Radar field:
DomainVerifier, an ownership check someone asked us to run on a domain;DisclosureContact, the security.txt lookup we make before emailing a security contact; andSurfaceScanner-DBVerify, the database check described under Requests that carry no receipt below. - • A Surface Scanner receipt covers one whole scan. It attests the scan's ID, the domain scanned, the time the scan started, and the domain of the email address the scan was requested with, if one was given. A scan started from our website does not ask for an email address, and the receipt of a scan a signed-in customer starts inside the EchelonGraph app carries none; a scheduled re-scan for a subscriber carries the subscriber's. It does not attest the host or port of each request. Every HTTP request the scan sends to the scanned domain carries the same receipt, and so do its requests to other hosts that belong to the scan, such as a host the domain redirected us to, one of its subdomains (
www.,mta-sts.), a script or storage bucket its pages reference, or a storage bucket named after it. The scan's requests follow the time on the receipt within five minutes, so a request carrying it later than that was not us.
Requests that carry no receipt
Some of our requests cannot carry a receipt, or do not go to you:
- • DNS queries. A DNS query has no headers. That includes the Surface Scanner's zone-transfer (AXFR) requests to the nameservers your domain publishes, and the name lookups our Discovery Radars make.
- • TLS handshakes. The Surface Scanner opens TLS connections to your web server to test which protocol versions and cipher suites it accepts. A handshake has no headers.
- • The mail check. The Surface Scanner connects to up to ten of your mail exchangers on port 25 to test STARTTLS, and names itself only in its greeting:
EHLO scanner.echelongraph.io. - • Lookups to data services. The Surface Scanner asks public services about your domain: Cloudflare's DNS-over-HTTPS resolver, the certificate-transparency search services crt.sh and Cert Spotter, domain registries' RDAP servers, and hstspreload.org. Those requests name us in the User-Agent and carry no receipt, because they go to those services, not to you. The same is true of our Discovery Radars' queries to data sources such as crt.sh.
- • The port check. A Surface Scanner scan that a signed-in customer starts inside the EchelonGraph app also opens a TCP connection to each port on a fixed list of common service ports on your host (database, remote-access and web ports, for example) to see which are open, and on a few, such as SSH and FTP, reads the greeting the service sends first. A connection has no headers. Where MongoDB, Redis, Memcached or Cassandra answers on its usual port, the scan holds a short exchange with it (such as Redis
INFO, or MongoDB's list of database names) to test whether it answers without a password, and reads no keys, documents or rows. The MongoDB, Redis and Cassandra exchanges carry a Discovery Radar-style receipt in the client details those protocols record (Redis's client name, the driver version for MongoDB and Cassandra), which verifies here asSurfaceScanner-DBVerify; Memcached has no such field, so its exchange carries none.
Other requests that name us
Some of our software names us in its User-Agent and is not a scan, so it carries no receipt and the rule above does not cover it: the CVE reference fetcher (EchelonGraph-CVE-Enricher), which reads the pages a published CVE record cites, up to ten per record; the collectors that download vulnerability and threat data, and our own package's download counts and release metadata, from the services that publish them; the jobs that check our own CVE data against NVD and other CVE services; webhook and SIEM deliveries and remediation pull requests a customer set up; and software customers run in their own environment. Our Responsible Disclosure page lists their User-Agents and what each one sends.
What our requests do
We never log in, submit credentials or exploit what we find. Not every request is a read, though.
The Surface Scanner makes one kind of write. For each storage bucket your site's pages reference (up to fifty) that exists, it uploads an empty object named .echelon-probe- followed by random characters, to test whether the bucket accepts anonymous writes. If the upload succeeds, it deletes the object straight away. If the bucket accepts the upload but refuses the delete, that zero-byte file may remain, and you can delete it safely at any time. It never does this to a bucket whose name it guessed. The scan is also broader than HTTP: it queries your DNS, including zone-transfer (AXFR) requests, and connects to your mail exchangers on port 25 to test STARTTLS.
Some Discovery Radar checks are not plain reads either: confirming an MCP endpoint takes one or two POSTs (server/discover, then the older initialize handshake only if that is refused), a DELETE only if a POST opened a session, and in some cases up to two metadata GETs or a GET of the older SSE stream; confirming an exposed datastore sends it a protocol command. Our Responsible Disclosure & Data Transparency policy lists what each radar sends and how to opt out, and the Terms describe every kind of Surface Scanner request.