whisper.identify() — Identity & Assessment
Answer whose infrastructure a host is, whether it's dangerous, and what sits around it. The identity family: identify, assess, walk.
On this page (6)
whisper.identify() — Identity & Assessment Documentation
Three procedures answer the questions you ask about a host before you decide what to do with it: whose infrastructure is this, is it dangerous, and — when neither has a clean answer — what sits around it. They are built to run over a batch of hosts and return decision-ready columns, so they slot straight into triage and enrichment pipelines.
whisper.identify(hosts) — whose infrastructure is this
whisper.identify() resolves a host to the vendor or service that operates it. Pass one host as a string or a batch as a list (large batches are rejected, not truncated; a URL folds to its host) and it returns the canonical identity plus the evidence behind it.
CALL whisper.identify(["api.stripe.com", "cdn.shopify.com"])
YIELD host, vendor_id, canonical_name, is_canonical, confidence, category, roles, host_class, band
RETURN host, vendor_id, canonical_name, is_canonical, confidence, category, host_class, band
The row is host, vendor_id, canonical_name, is_canonical, confidence, category, roles, band, host_class, evidence:
| Column | Meaning |
|---|---|
host | The input host, with any URL folded to its hostname. |
vendor_id | Stable identifier of the resolved vendor (github, aws, …); null when nothing was reconciled. |
canonical_name | The operator this host resolves to — not the brand in the name. Read it with is_canonical. |
is_canonical | true when canonical_name is this host's resolved operator; false when it is the nearest known vendor, offered as a lead rather than an identity. |
confidence | How strongly the evidence supports the identity, 0.0–1.0. It tracks the band. |
category | What kind of service it is (saas, cloud, cdn, …). |
roles | The roles the host plays (DNS_OPERATOR, MAIL_RECEIVER, ORIGIN_AS, …). |
band | Attribution band — how the identity was reached. See the ladder below. |
host_class | A coarse classification of the host — multi_tenant_user_content flags platforms where anyone can publish. |
evidence | The signals that produced the match, as a path list (RESOLVES_TO->IPV4->DELEGATED_TO->VENDOR:aws). Its last element restates the band. |
Six company columns follow evidence: company_apex, company_linkage, company_name, company_sector, company_hq_country and pl_generation. They were appended, so code that reads the earlier columns by position is unaffected. Without company data on your access, company_name, company_sector and company_hq_country come back empty, and company_linkage only says whether a company is linked.
A bare IP works as well as a hostname: it is attributed through the address's DELEGATED_TO vendor or its BGP origin network.
CALL whisper.identify(["https://github.com/torvalds/linux", "8.8.8.8"])
YIELD host, vendor_id, canonical_name, is_canonical, confidence, band, evidence
RETURN host, vendor_id, canonical_name, is_canonical, confidence, band, evidence
Use it to label infrastructure at scale — attributing a list of hostnames from a log or an alert to the services behind them, without a hop-by-hop traversal per host.
canonical_name is the operator, not the brand
This is the single most common misreading, and it is not an edge case. Live on 2026-09-02:
| You ask for | canonical_name | is_canonical | band | Read it as |
|---|---|---|---|---|
stripe.com | Aws | true | DERIVED | Stripe's edge resolves into AWS address space. Correct, and not the brand. |
paypal.com | Fastly | true | DERIVED | Fronted by Fastly. Correct, and not the brand. |
paypa1.com | Aws | true | DERIVED | A look-alike hosted on a large cloud gets a confident attribution to that cloud. That is who runs it, not evidence that it is genuine. |
githqb.com | akamai | false | HEURISTIC | The nearest known vendor, offered as a lead; vendor_id is null. |
amazon.com | null | null | UNKNOWN | Unattributed here. Never read UNKNOWN as suspect. |
whisper.identify() answers whose infrastructure this host runs on. It does not answer whose brand this is, and for anything behind a CDN or a cloud the two are different companies. Always read canonical_name together with is_canonical: when is_canonical is false the string is the nearest known vendor, not this host's operator, and pasting it into a ticket as an owner is wrong. None of this is a threat verdict either: a look-alike on a reputable cloud gets the same confident attribution as the brand it imitates.
The attribution bands
band says how the identity was reached, and confidence follows it. Strongest first:
| Band | confidence | vendor_id | is_canonical | What it means |
|---|---|---|---|---|
DIRECT | above the DERIVED range | set | true | A direct match on the host itself. |
DERIVED | 0.70–0.89, a range | set | true | Reached by traversal — RESOLVES_TO -> IPV4 -> DELEGATED_TO -> VENDOR, or an origin-AS organisation plus a curated alias. The normal answer for a host that resolves. |
HEURISTIC | 0.4 | null | false | A resemblance from one weak signal, usually an origin-AS organisation name with nothing to reconcile it against. A lead, not an attribution: corroborate before acting. |
UNKNOWN | 0.0 | null | null | No match. Every other column is null. This is a populated row, not an error and not an empty result — the batch never fails because one host is unknown. |
Two rules for anything consuming this:
- Branch on the band, not on the confidence number. The float is informative and
DERIVEDis a range, so do not threshold on a single value; the band is the contract. - Treat the vocabulary as open. Handle a band you do not recognise the way you handle
HEURISTIC— as needing corroboration — rather than trusting it or dropping the row.
An UNKNOWN host is where whisper.walk() (below) earns its place: it returns the structural neighbourhood so you have something to look at instead of a blank.
whisper.assess(hosts) — is it dangerous
whisper.assess() answers the safety question, and — critically — tells you what it actually looked at. It accepts a single host as a bare string or a batch as a list, and a URL folds to its host. Every form returns the same columns: host, label, band, sub_labels, signals, coverage, evidence, verdictScore, isThreat, threatSources, then the six company columns described under whisper.identify above, with the same gaps on an access without company data. CALL whisper.assess("github.com") returns coverage: known-clean with a benign label.
CALL whisper.assess(["example.com", "185.220.101.1"])
YIELD host, label, band, sub_labels, signals, coverage, evidence, verdictScore
RETURN host, label, band, coverage, verdictScore
| Column | Meaning |
|---|---|
label | The assessment (benign-allowlisted, ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways., …). |
band | Severity band (NONE … CRITICAL). |
sub_labels | Finer-grained labels behind the top-level label. |
signals | The signals that fired, each with its source, kind, class and confidence. |
coverage | What we looked at, on the malice question. This is the column to gate on, and it is not a strength scale. See Coverage. |
evidence | The underlying evidence, as key:value strings (coverage:ambiguous, feed-source-count:6). |
verdictScore | The reconciled verdict score, the same number explain() returns as verdictScore. |
isThreat | The threat flag the host's node carries. It can read false on a host that feeds list, so gate on coverage, not on this flag. |
threatSources | How many feeds list the host, the same count its node carries: a number, not the feed names. |
Yield only these columns: any other name is rejected, and the error names the valid columns.
A CVE id is accepted too. The row then speaks the vulnerability vocabulary: label reads the exploitation status (kev-exploited, for instance), evidence[] carries the KEV, EPSS and CVSS records, and coverage reads known-cve rather than one of the four host values.
Every Whisper verdict answers two independent questions.
bandtells you how bad.coveragetells you what we actually looked at. Read both. They are a grid, not a ladder.
Only known-clean — coverage: known-clean. In coverage, no malicious evidence. licenses the word "clean". Every other value is not-clean — and no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. and
deadline-hit mean unknown, which is a different thing again.
whisper.assess, whisper.assessUrl, whisper.explain and whisper.walk all return coverage, and it is not about the same thing in each.
coverage | What it means | What to do |
|---|---|---|
| known-clean — coverage: known-clean. In coverage, no malicious evidence. | We hold data at this granularity and nothing malicious is in it. | Treat as clean. This is the only value that licenses closing a ticket on "clean." |
| malicious-evidenced — coverage: malicious-evidenced. In coverage, with positive evidence of malice. | Some positive evidence of malice exists. It may be a single feed at weight 0.5. It does not mean the band is high. | Read evidence[] for feed-source-count, then run explain() for the per-feed provenance, weights and timestamps. A count of 1 on a low-weight aggregate list is a lead, not a finding. |
| ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways. | The evidence points both ways — for example an anonymising-egress signal alongside generic abuse listings. | Escalate to a human. Do not automate a decision on this value. |
| no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. | We have never observed this host. | Unknown. Never benign. Ask a different question — the container, the operator, the age — and escalate with "we have no observation of this host", never with "it came back clean." |
Every one of these arrives as a populated row. no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. is a row that says no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at.; it is never an empty result set. If a query returns zero rows, the first hypothesis is that the query is wrong, not that the host is clean.
Which procedure carries coverage:
| Procedure | Returns coverage? | What its coverage is about |
|---|---|---|
whisper.assess | Yes | Threat coverage. The four values above. |
whisper.assessUrl | Yes | A path axis, not a host axis — read the contract before gating on it. |
whisper.walk | Yes, but not a verdict | Atlas and vendor adjacency — whether the host is reachable in the graph's structure. Emits presence-axis values only. |
whisper.explain | Yes | Threat coverage; for a host or an address, the same value whisper.assess returns. A NONE level from explain() is a score, not a clean verdict: read coverage beside it. |
structural-onlywalk — a whisper.walk value describing atlas adjacency. Not an assess coverage value. Do not gate on it. is a whisper.walk value describing atlas adjacency. It is not a
whisper.assess value, and a branch keyed on it in an assess result is unreachable — see
the full contract.
whisper.walk(host [, depth] [, budget_ms]) — the structural neighborhood
When whisper.identify() has no direct match, whisper.walk() returns a bounded structural neighborhood — the host's siblings and the nearest known vendors — so you still get context to reason about an unknown host. It is depth- and budget-bounded so it stays fast on large networks.
CALL whisper.walk("unknown-host.example", 2, 800)
YIELD host, no_atlas_match, nearest_known_vendors, siblings, coverage, arms
RETURN host, no_atlas_match, nearest_known_vendors, coverage, arms
The row is host, no_atlas_match, nearest_known_vendors, siblings, coverage, arms:
| Column | Meaning |
|---|---|
no_atlas_match | true when the host resolved to no direct vendor identity. |
nearest_known_vendors | The closest identified vendors in the surrounding infrastructure, each with vendor_id, canonical_name, is_canonical, confidence, the channel it was inferred through (DELEGATED_TO, ORIGIN_AS, DNS_OPERATOR) and a band. Read the confidence; do not take the first row. |
siblings | Hosts that share infrastructure with the input. |
coverage | How much of the neighborhood the walk reached: structural-onlywalk — a whisper.walk value describing atlas adjacency. Not an assess coverage value. Do not gate on it. when the answer rests on graph structure, no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. when nothing was reachable, deadline-hit when the budget ran out first. A presence value, not a verdict. It is not an assess value, and it uses a different vocabulary. Read arms before you read it. |
arms | Per-arm traversal detail: arms_completed, arms_truncated, arms_excluded and deadline_hit. |
The optional second argument is the traversal depth and the third a per-arm time budget in milliseconds; both default to modest values. When the budget runs out, the row comes back with what was reached and arms.deadline_hit: true — a cut-short walk is a result, not an error. Keep both modest — this is a fallback for the case where a direct identity lookup came back empty, not a general graph crawler. A URL folds to its host here as well.
The same word, two meanings. On one indicator the two procedures answer differently, and both are right:
CALL whisper.assess(["185.220.101.1"])→coverage: "ambiguous"— there is evidence, pointing both ways.CALL whisper.walk("185.220.101.1")→coverage: "no-data"— no atlas match, andarms_completed: 7with nothing truncated.
assessreports threat coverage;walkreports structural adjacency. Never gate a verdict on awalkrow. A branch written againstwalk's vocabulary in anassessresult is unreachable, and the branch it leaves you missing is malicious-evidenced — coverage: malicious-evidenced. In coverage, with positive evidence of malice..
Where these fit
- Start with
identifyto attribute known infrastructure. - Use
assessfor the safety verdict on a batch, and always readcoveragebefore you act onlabel. - Fall back to
walkonly whenidentifyreturns no match and you need surrounding context.
For a single scored threat verdict with per-feed evidence, use explain(). To band a full URL rather than a host, use whisper.assessUrl() — and read its page first, because its coverage column describes the path, not the host. For the full procedure catalog, see the Procedures overview.