WhisperDocs
Endpoint

Fleet, logs and console

Where you see and govern your fleet: every endpoint's connectivity and sensor liveness, its per-process trust, its detections, its activity, and five identity-anchored policy shapes, all on one verb. Read it with stock tools, or in the operator console.

The fleet surface is one control verb, whisper.agents, addressed by an operation and a kind. It is two-tier by design, the same as everything else on the network. The keyless checks, verify an identity, look up an RDAP record, ask the graph for a verdict, need no account. The control-plane reads and writes, your fleet, its process census, its detections, its logs and its policy, take your API key, and the key stays server-side: it never leaves the backend, and the console never sees it.

# the control plane. The key stays server-side; the console never holds it.
$ curl -s https://graph.whisper.online/api/query \
    -H 'content-type: application/json' \
    -H 'X-API-Key: whisper_live_...redacted' \
    --data '{"query":"CALL whisper.agents({op:\'list\', kind:\'agents\'})"}'

The fleet at a glance

op:list with kind:'agents' returns every identity on the wire, one row per /128, with its label, forward name, address, connectivity tier, sensor liveness and nesting. It is the roster the console draws from.

# envelope from a real run (control plane, key redacted)
# op=list  ok=true  status=200  rows=320  elapsed_ms=324
# columns: label · fqdn · address · tier · sensor · parent · children · depth

Two facts on this screen carry the whole model. First, connectivity tier and sensor depth are separate columns: a host can be covered at the network layer with no on-host sensor at all. Second, sensor depth is the depth of on-host visibility, never the depth of protection: every /128 carries resolution policy and the off-host kill-switch whether or not a sensor is shipping.

The nesting columns make the roster a tree, not a list. Each row reports its parent, its children (fan-out capped in the report, with the surplus counted rather than dropped) and its depth, and the per-owner rollup is served both raw and nested-deducted, so an owner whose one parent agent spawned two hundred sub-agents reads as one root with a subtree, not as two hundred strangers. What nesting means for policy is below, under Nesting and subtree revoke.

The Whisper EDR operator console Overview. Header reads 53 endpoints, 318 on the wire. Fleet coverage shows connectivity tiers of 3 routed, 20 egress and 30 resolver, and on-host sensor depth of 36 shipping, 4 stale and 13 none. Twenty-five detections are held and zero contained. A measured-calm vector reads under 1% CPU. Identities by reputation band shows most identities unscored, labelled quiet is a real measurement, unscored is not yet resident, never assumed safe.

The operator console Overview, on demo data. Every /128 carries policy and containment at the network layer, sensor or not. The reputation band panel does not round up: unscored means not yet resident, never assumed safe.

The per-process census

Drill into a single host with kind:'proc' and an address, and you get its live process census: one row per process with a trust tier, a signal list, and a hash verdict. Known-bad binaries from the threat graph read as confirmed bad; known-good OS binaries are affirmatively cleared, so an analyst sees the calm majority as a positive signal, not just the red. The columns are subject · exe · score · signals · band · owner · country · asn · prevalence · coverage · trust · trustClass · knownGood · knownGoodOnHost.

# Host: web-edge-01   agent-a45b8b2414f9156e2
# /128: 2a04:2a01:8dae:4521:45b8:b241:4f91:56e2

$ curl -s https://graph.whisper.online/api/query \
    -H 'content-type: application/json' \
    -H 'X-API-Key: whisper_live_...redacted' \
    --data '{"query":"CALL whisper.agents({op:\'list\', kind:\'proc\',
             address:\'2a04:2a01:8dae:4521:45b8:b241:4f91:56e2\', limit:500})"}'

envelope: op=list  ok=true  status=200  elapsed_ms=66
processes returned                        : 500
hash-verdict known-good (fleet allowlist) : 52
known-good on THIS host                   : 54
carrying a signal                         : 62

# known-good rows - the affirmative calm signal, not just red
proc=1695119:87841819  trust=T3/unknown  knownGood=true  onHost=true  exe=/usr/bin/wg  signals=[hash-verdict:known-good,hash-verdict:known-good-onhost]
proc=1695161:87842331  trust=T3/unknown  knownGood=true  onHost=true  exe=/usr/bin/ip  signals=[hash-verdict:known-good,hash-verdict:known-good-onhost]

# signal-carrying rows - fan-out + sensor reputation, observe-only
proc=705:1107          score=48  trust=T2/unknown  signals=[fan-out:279,sensor-rep:48]
proc=1682122:0         score=48  trust=T2/unknown  signals=[fan-out:26,sensor-rep:48]
i

The local known-good clamp is seeded from a bundled allowlist of OS-binary hashes and grows from the fleet's own verified binaries, so the affirmative "known-good" reading is computed self-contained, then cross-checked against internet-scale hash reputation on the graph. A trust tier and a signal are observations. They are not an action: nothing here is blocked. Turning a tier into an egress consequence is a policy choice, below under Trust-graded egress; turning a verdict into a response is covered under Containment.

The detection ledger, held not contained

op:list with kind:'task' is the detection ledger: every technique the analytic layer fired, ranked worst-exposed-first. The tail columns are exposure and fusedPriority, which multiply a technique's static severity by how exposed the process actually is right now (executed, network-reachable, dormant), so a live seed of three ransomware detections ranks 50.0, 45.0 and 42.5, the running and reachable one on top.

Every row says the same word: held. The sensor observed the technique and did not act. Cutting the /128 is the operator's call, and it works out of band. We never label a row "contained" that the product did not contain.

The console Incidents view, the full detection ledger on demo data. Twenty-five detections are held across thirteen hosts, oldest held seventeen days ago, one severed and still held. Filter chips show two critical, one high, twenty-two held. Rows list hosts with their /128 address, an ATT&CK technique such as T1486 or T1055, signal chips such as high_entropy, mass_write and mass_delete, an age, and a status that every row repeats as held, not contained.

The Incidents ledger, on demo data. The subhead states the contract in the product's own words: every row is held, not contained, the sensor observed and did not act, and cutting the /128 is your call and works out of band. Detections tee to the server task plane, so the console and a plain op:list read the same ledger.

Per-agent activity: op:logs

op:logs streams a tenant's per-agent activity, newest first, epoch-milliseconds, on a fixed eighteen-column contract. Each row follows one lookup end to end: a masked client source, the Whisper identity that made it, the resolved name with its type, response code and policy decision, then the peer and the bytes and packets up and down. Activity is keyed to a routable /128, so "which agent did this" is answerable from the record itself.

$ curl -s https://graph.whisper.online/api/query \
    -H 'content-type: application/json' \
    -H 'X-API-Key: whisper_live_...redacted' \
    --data '{"query":"CALL whisper.agents({op:'"'"'logs'"'"', address:'"'"'{addr}'"'"'})"}'
# columns: ts · client_src(masked) · identity · qname · qtype · rcode · decision · peer · bytes_up · bytes_down · packets · ...
i

The same activity is served for a single device, no key required, at endpoint.whisper.online/logs/{device-token}, a readable per-device history for the owner of that token.

Governance: five policy shapes, one verb

Policy here has one through-line that makes every shape below different from a host firewall or an MDM profile: it is keyed by the agent's routable /128 and enforced off the agent's host, in Whisper's resolver and egress plane. A compromised endpoint cannot edit, disable or even see its own enforcement point. Five shapes, all expressed through the same whisper.agents verb with your API key:

1 · The per-agent egress firewall live

Each /128 can carry an ordered allow/deny rule-set the egress plane consults once per outbound connection, before the socket opens. Rules match a destination ip, cidr, host (exact, or any-subdomain suffix) or port, first match wins, then a default action:

# default-deny this agent, allow exactly its job
op:firewall {address:'</128>', default:'deny',
             rules:[{action:'allow', match:'host', value:'api.stripe.com'},
                    {action:'allow', match:'port', value:'443'},
                    {action:'deny',  match:'cidr', value:'10.0.0.0/8'}]}
# omit rules to read the current set back

Because the check runs in Whisper's egress plane, a root-level compromise of the agent's host changes nothing: the block is not on the box. Every rule-set write lands in the durable containment audit trail on the same footing as a revoke, and revoking an identity drops its rule-set, so a recycled address can never inherit a stale ACL. An address you do not own reads back as a clean not-found, never as another tenant's rules.

Two more egress governors ride the same shape: per-agent byte caps (up, down, or both, tightest-wins against any budget cap) and a per-agent budget (bytes, connections, requests) whose kill:true cuts the agent's live tunnels immediately. And for automation, a per-run scope: a windowed, deny-default destination allowlist bound to one run's start and end, so a CI job or an agent task can reach exactly its declared destinations for exactly its declared window.

2 · Trust-graded egress live

The census above buckets every observed process into a trust tier, T0 known-bad up to T4 verified, and only a confirmed graph verdict can reach T0, never a fail-open guess. Policy maps each tier onto an ordered ladder of egress freedom, and the plane enforces the mapping off the endpoint's worst currently-observed tier:

The egress-freedom ladder: none, dns-only, mediated, direct, unrestricted. Trust tiers map onto it; a known-bad process observed on a host drags that identity's egress down the ladder, enforced in the network. none nothing leaves dns-only resolve, no connect mediated through the proxy direct routed /128 unrestricted earned by T4 a T0 known-bad process observed on the host drags the identity down the ladder · enforced in the network, not on the box
The freedom ladder. Map each trust tier to a rung; the plane folds the endpoint's worst observed per-process tier and stages the consequence at the egress proxy. A deliberate inversion (a stricter mapping for a higher tier) is stored with a warning, because the mapping is yours.
# a known-bad process anywhere on the host? Its egress drops to dns-only.
op:policy {trust.T0:'none', trust.T1:'dns-only', trust.T4:'unrestricted'}

This is a decision no host-only product can ground: a "known-bad process on this endpoint, so this identity's egress narrows" call, made with graph-confirmed reputation, in a plane the compromised host cannot reach.

3 · Geography, two distinct planes

Resolution-plane geo policy live is graph-backed and evaluated per lookup: a deny-list or an allow-list of country codes, applied to where a name actually resolves, with deny winning on any conflict. Because it runs in the resolver, the answer is governed before a packet ever leaves for the destination, from live graph geo-intelligence rather than a downstream GeoIP box:

op:policy {bundles:['geo:deny:RU,KP','block:tor-exits']}   # deny-list
op:policy {bundles:['geo:allow:NL,DE,US']}                 # allow-list · deny always wins

Identity-plane jurisdiction tagging disabled by default · enabled per deployment goes further: the /128 itself is allocated inside a country-tagged sub-block of your prefix, and the tag is published in the identity's RDAP record and the standard geofeed. Jurisdiction becomes a verifiable property of the address a regulator can read off the wire. It is off by default, allocation stays byte-identical until it is enabled for your deployment (ask us), and a jurisdiction lookup can never block an allocation.

4 · Per-device policy live

Any op:policy call takes an optional device selector, an agent id or a /128, and the same body then governs one identity instead of the whole tenant: allow and deny lists, named graph bundles, resolution mode, all applied live at the resolver by source address, so policy follows the identity rather than a subnet or a config file:

op:policy {address:'</128>', block:['*.tiktok.com'], default:'allow', mode:'graph-only'}

Precedence is safety-shaped: a per-device policy only ever escalates, so a graph-critical sinkhole can never be downgraded for one device, and per-device calm settings clamp quieter-only against the tenant floor. Log retention stays per-account by design. Revoking a device drops its override, so a recycled address cannot leak one tenant's policy into another's.

5 · Nesting and subtree revoke live

Agents nest. A machine that is a Whisper agent runs a browser that is also a Whisper agent egressing through it; a parent AI agent spawns sub-agents; each nested identity gets its own governed /128, at any depth. The tree is formed two ways: declared at spawn, and measured, because the egress plane physically observes the child's traffic arriving from the parent's address. Enforcement trusts only the measured chain, so a hostile agent cannot name a victim as its parent to inherit or probe its policy.

A nesting tree: a parent /128 with two children and a grandchild, edges labelled measured. A kill switch flipped on the parent greys out the entire subtree: every descendant's next connection is refused, because enforcement walks the measured transit chain. parent · /128 kill: true child A · own /128 next CONNECT → deny child B · own /128 next CONNECT → deny grandchild · own /128 next CONNECT → deny measured measured measured cut the parent, cut the branch · enforcement walks the measured transit chain, so a deny or kill at any ancestor binds every descendant behind it
Subtree revoke. Flip the kill on the parent and every descendant transiting it is refused on its next connection; you never chase the children individually. A hard revoke of the parent's identity removes the transit path the same way.
i

Maturity. The firewall, trust-graded egress, byte caps, budgets and kill, run scopes, resolution geo bundles, per-device policy and the whole nesting surface above are live-enforced today; jurisdiction-tagged allocation is opt-in and disabled by default. The tunable calm "personality" (how assertive and how loud the sensor is, per fleet or per device) round-trips through the same verb and is tagged stored in the read-back while its enforcers land; nothing stored ever renders as live policy. See Calm footprint and SLA for that surface.

The operator console

The console is the same reads, drawn. Its Hunt surface runs structured queries over the live control plane, fleet identities, detections, process graphs and internet-scale graph reputation, and its limits are explicit: it is read-only and tenant-scoped, the key stays server-side, and it cannot register, revoke, or change policy. Those are deliberate control-plane actions, not a console button.

The console Hunt surface on demo data. A left panel selects op list and kind agents with optional address and limit fields and a Run query button, plus saved queries for fleet identities, open detections, graph reputation on adversary infrastructure, a process graph, a write to exec to connect chain, and ATT&CK coverage. The result header reads whisper.agents op list kind agents, 320 rows, 324 milliseconds. A table lists agent labels, forward names and /128 addresses. The top-right note reads read-only, tenant-scoped, key stays server-side.

Hunt, on demo data. The saved queries map one-to-one onto the control-plane reads on this page: fleet identities, the detection ledger, graph reputation, and the per-endpoint process graph.

Fleets of fleets, and self-serve

Two consoles sit on top of these reads. Partners govern an agent fleet like a roster of employees, each agent with an owner, a time-to-live and an attestable state, worst-first, rolling an MSSP up over many tenants with white-label reporting; this is live in production. And a signed-in consumer gets a self-serve devices view that puts each device's verifiable identity at the centre of a live, graph-enriched map of what it is talking to. The operator EDR console that draws the screenshots above is in preview behind an operator gate; the control-plane verbs it reads are live.

Two tiers, one verb

Everything here is the same verb seen from two distances. With no key, anyone can verify an identity, read its RDAP record and ask the graph for a verdict: real value, zero friction, no account. With your key, the control plane opens: the roster, the process census, the detection ledger, the logs, and all five policy shapes, worldwide, and the key never leaves the backend. See Verify an identity for the keyless half, and Containment for turning a held detection into a response.