Coverage — what we looked at
Every Whisper verdict answers two questions: how bad, and what we actually looked at. Read coverage before band.
On this page (6)
Coverage — what we looked at Documentation
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 and whisper.assessUrl return coverage. whisper.explain does not — see
the procedure contract below before you rely on either.
The four values whisper.assess returns
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. Follow the no-data playbook — do not simply escalate. |
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.
The case that proves the rule
CALL whisper.assess(["140.82.121.3"]) YIELD host, band, coverage, evidence
RETURN host, band, coverage, evidence
{"host":"140.82.121.3","band":"LOW","coverage":"malicious-evidenced",
"evidence":["coverage:malicious-evidenced","band:LOW","host-class:unknown",
"feed-source:listed","feed-source-count:1"]}
band: LOW beside positive evidence of malice. A pipeline that gates on band alone treats this row
as unremarkable. The band is low because the verdict reconciler de-escalates for a multi-tenant apex,
for evidence older than 90 days, and for a zero base score — none of which change what we looked at.
That is why the rule is key on coverage, never on band.
no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. is common — here is what to do with it
no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. is not exotic: CALL whisper.assess(["104.16.132.229","104.16.123.96"])
— two ordinary Cloudflare edge addresses — returns band: UNKNOWN, coverage: no-data for both.
Hostnames that appear in DNS generally carry real coverage; a bare IPv4 address often does not.
So "escalate everything that says no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at." is not a workable rule at volume, and we are not going to pretend it is. When you get no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at., ask a different question rather than a louder one.
- Ask about the container.
CALL explain("<enclosing /24>")and the announcing ASN. A host we have never seen inside a heavily-listed block is a different fact from the same host inside a clean one. (On a CIDR or ASN indicator, readexplanationandfactors[]alongsidescore: they carry the network-level finding — how many listed addresses and subnets the block contains, and its threat density. See the procedure contract.) - Ask who runs it.
CALL whisper.identify(["<host>"]). A no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. verdict on infrastructure that identifies as a known SaaS vendor is expected. On infrastructure that identifies as nothing, the no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. is the finding. - Ask when it appeared.
CALL whisper.history.whois("<domain>"). A domain registered this week has no feed history by construction — that is the signal, not the absence of one. - Only then escalate — and escalate with the sentence "we have no observation of this host", never with "it came back clean."
The other three declared values
The coverage vocabulary has seven values. whisper.assess emits four of them. The other three are
real, and they are not assess values.
| Value | Where it is observed | Treat it as |
|---|---|---|
deadline-hit | From whisper.walk. CALL whisper.walk("google.com", 2, 1) (a one-millisecond budget) returns coverage: "deadline-hit" with arms.deadline_hit: true | Not a verdict. The time budget expired before coverage could be established. Retry with a larger budget. Never read it as clean and never read it as bad |
partial | Not observed by any probe run for this page | Not-clean. We covered only part of what you asked about — narrow the scope and re-ask |
| structural-onlywalk — a whisper.walk value describing atlas adjacency. Not an assess coverage value. Do not gate on it. | From whisper.walk only. See below | Not a verdict at all. Do not gate on it |
Treat any value other than known-clean — coverage: known-clean. In coverage, no malicious evidence. as not-clean, and no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at., deadline-hit and partial
as unknown, which is a different thing again.
We have not observed partial, and we are not asserting it is unreachable. If a later measurement
produces it, this table gains a row.
structural-onlywalk — a whisper.walk value describing atlas adjacency. Not an assess coverage value. Do not gate on it. is a whisper.walk value. It is not an assess value.
Do not gate on structural-onlywalk — a whisper.walk value describing atlas adjacency. Not an assess coverage value. Do not gate on it.. If your code has a branch for it on an assess result, that
branch is unreachable — and the branch you are missing is malicious-evidenced — coverage: malicious-evidenced. In coverage, with positive evidence of malice..
The seven values split across two axes, and the split is enforced in the engine:
| Axis | Values | What a value asserts |
|---|---|---|
| Verdict axis | known-clean — coverage: known-clean. In coverage, no malicious evidence. · malicious-evidenced — coverage: malicious-evidenced. In coverage, with positive evidence of malice. · ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways. | A claim about malice |
| Presence axis | structural-onlywalk — a whisper.walk value describing atlas adjacency. Not an assess coverage value. Do not gate on it. · partial · no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. · deadline-hit | A claim about whether we looked — and nothing about malice |
whisper.assess is a verdict procedure: it emits the three verdict-axis values, plus no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at..
whisper.walk is not a verdict procedure: it may emit presence-axis values only, so that a tool
which is not authorised to render a verdict cannot leak one through this vocabulary.
whisper.walk's coverage therefore describes atlas and vendor adjacency, not threat coverage —
whether the host is reachable in the graph's structure. Its full column set makes this explicit
(no_atlas_match, nearest_known_vendors, siblings, arms).
The same indicator can carry two different coverage values from two different procedures, and for one indicator the two flatly disagree.
CALL whisper.assess(["185.220.101.1"]) -> coverage: "ambiguous" (there is evidence)
CALL whisper.walk("185.220.101.1") -> coverage: "no-data" (no atlas match)
Both are correct. They answer different questions. Read the procedure before you read the column.
One more trap in walk: a truncated walk still returns structural-onlywalk — a whisper.walk value describing atlas adjacency. Not an assess coverage value. Do not gate on it..
CALL whisper.walk("github.com", 2, 3) returns coverage: "structural-only" with
arms: {arms_completed: 7, arms_truncated: 5, arms_excluded: 1, deadline_hit: false} — five arms
dropped and no deadline flag. Read arms before you read coverage on a walk row.
Which procedure carries coverage
| Procedure | Returns coverage? | What its coverage is about, and the gotcha |
|---|---|---|
whisper.assess | Yes | Threat coverage — the four values above. Its output contract is host, label, band, sub_labels, signals, coverage, evidence, verdictScore, isThreat, threatSources; YIELD anything else and you get HTTP 400 query-error naming the valid columns. |
whisper.assessUrl | Yes | A path axis, not a host axis. Its coverage describes what was checked about the URL path, so the standing rule does not transfer unchanged — read this row before gating on it, and prefer whisper.assess for any decision that turns on host coverage. |
whisper.walk | Yes, but not a verdict | Atlas and vendor adjacency. Presence-axis values only. Read arms first. |
whisper.explain | No | Returns indicator, type, available, cached, found, score, level, explanation, factors, sources, advisory. There is no coverage column at all, so a level: NONE from explain() is not a clean verdict — it is a score, and the question of whether we looked is simply unanswered. |
Two gotchas on explain() worth stating plainly:
- On a CIDR or an ASN,
scoreandleveldescribe the whole network, andexplanationandfactors[]say why.CALL explain("3.64.0.0/12")returns aCRITICALlevel with an explanation that counts the listed addresses and subnets inside the block and gives its threat density. Read the explanation before you act on a network-level score: a large block with a modest density is a different fact from a small block that is listed end to end. advisoryis usually null and occasionally load-bearing.explain("1.1.1.1")returnsadvisory: "allowlist-vouched"; most indicators return null. Select it by name if you need it, and do not treat a blank as "no advisory applies" without checking the indicator type.
The rule key on coverage, never on band is published per procedure, never as a blanket
rule, because it is only true of the procedures that return a coverage column.