Skip to content
WhisperGraph
Skip navigation

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.

Published Last updated

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.

cypher · runnablegraph.whisper.securitySign in to run
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:

ColumnMeaning
hostThe input host, with any URL folded to its hostname.
vendor_idStable identifier of the resolved vendor (github, aws, …); null when nothing was reconciled.
canonical_nameThe operator this host resolves to — not the brand in the name. Read it with is_canonical.
is_canonicaltrue 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.
confidenceHow strongly the evidence supports the identity, 0.0–1.0. It tracks the band.
categoryWhat kind of service it is (saas, cloud, cdn, …).
rolesThe roles the host plays (DNS_OPERATOR, MAIL_RECEIVER, ORIGIN_AS, …).
bandAttribution band — how the identity was reached. See the ladder below.
host_classA coarse classification of the host — multi_tenant_user_content flags platforms where anyone can publish.
evidenceThe 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.

cypher · runnablegraph.whisper.securitySign in to run
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 forcanonical_nameis_canonicalbandRead it as
stripe.comAwstrueDERIVEDStripe's edge resolves into AWS address space. Correct, and not the brand.
paypal.comFastlytrueDERIVEDFronted by Fastly. Correct, and not the brand.
paypa1.comAwstrueDERIVEDA 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.comakamaifalseHEURISTICThe nearest known vendor, offered as a lead; vendor_id is null.
amazon.comnullnullUNKNOWNUnattributed 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:

Bandconfidencevendor_idis_canonicalWhat it means
DIRECTabove the DERIVED rangesettrueA direct match on the host itself.
DERIVED0.70–0.89, a rangesettrueReached 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.
HEURISTIC0.4nullfalseA 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.
UNKNOWN0.0nullnullNo 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 DERIVED is 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.

cypher · runnablegraph.whisper.securitySign in to run
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

ColumnMeaning
labelThe assessment (benign-allowlisted, ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways., …).
bandSeverity band (NONE … CRITICAL).
sub_labelsFiner-grained labels behind the top-level label.
signalsThe signals that fired, each with its source, kind, class and confidence.
coverageWhat we looked at, on the malice question. This is the column to gate on, and it is not a strength scale. See Coverage.
evidenceThe underlying evidence, as key:value strings (coverage:ambiguous, feed-source-count:6).
verdictScoreThe reconciled verdict score, the same number explain() returns as verdictScore.
isThreatThe 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.
threatSourcesHow 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. band tells you how bad. coverage tells 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.

coverageWhat it meansWhat 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:

ProcedureReturns coverage?What its coverage is about
whisper.assessYesThreat coverage. The four values above.
whisper.assessUrlYesA path axis, not a host axis — read the contract before gating on it.
whisper.walkYes, but not a verdictAtlas and vendor adjacency — whether the host is reachable in the graph's structure. Emits presence-axis values only.
whisper.explainYesThreat 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.

cypher · runnablegraph.whisper.securitySign in to run
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:

ColumnMeaning
no_atlas_matchtrue when the host resolved to no direct vendor identity.
nearest_known_vendorsThe 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.
siblingsHosts that share infrastructure with the input.
coverageHow 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.
armsPer-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, and arms_completed: 7 with nothing truncated.

assess reports threat coverage; walk reports structural adjacency. Never gate a verdict on a walk row. A branch written against walk's vocabulary in an assess result 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 identify to attribute known infrastructure.
  • Use assess for the safety verdict on a batch, and always read coverage before you act on label.
  • Fall back to walk only when identify returns 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.