Shadow AI radar
Five evidence sources for AI activity that never touched the gateway, and a coverage model that refuses to call a dead feed a clean estate.
This one sits outside the eleven-step request path: it governs what happens around a governed call rather than inside one.
the whole pathOn this page
Nothing found and nothing looked at are the same empty list
Shadow AI discovery is sold as a detection problem and it is mostly a reporting-integrity problem. Any tool can produce a list of findings; the hard part is the sentence underneath the list when it is empty. A console that renders no findings as a green banner has made a claim about your estate, and on most days it has no idea whether it is entitled to. The export could have stopped three weeks ago. The credential it arrives on could have been revoked this morning. The exporter could be posting on schedule into a mapping that reads none of it. All four situations emit exactly the same bytes as an estate where nobody is bypassing the gateway.
That failure is not hypothetical and the product records the version of it that it shipped. Arrival used to be recorded as a batch arriving, which meant an exporter delivering an empty page every hour held its source at fresh indefinitely — with a row count of zero stored beside the claim, where no state machine read it. A wrong window, a page that came back empty, a permission quietly downgraded to one that returns nothing: those are the commonest ways a real exporter fails, and each of them is worse than having no feed at all, because coverage was affirmatively asserting freshness over the top.
The second half of the problem is that freshness alone is always late. The bound a feed may go quiet for has to be measured in cycles of its own export — a vendor master bill is published once a calendar month, so forty-five days of silence is the first gap that unambiguously means somebody stopped exporting. Which means a credential revoked this morning leaves its source reading fresh until September. The connector’s own verdict is available today. Reporting the later of two true answers when the earlier one is already in hand is the same class of lie as the green all-clear, just slower.
So Token Observe separates the three questions and reports them side by side rather than folding them into one number. Was anything found. Has this source been fed, and how recently. Is anything knocking, and what happens when it does. A surface showing only the first is a findings list; a surface showing only the first two is a findings list with a slower disguise.
How it actually works
- 01
Evidence arrives and its arrival is recorded before anything looks at it
Rows reach Token Observe through an ingest receiver on a credential it issued, or through an operator scan posted by hand, and either way the arrival is written as a fact in the database before a detector runs. It used to be implied by a scan having happened, which recorded that somebody asked a question rather than that there was anything to ask it about. - 02
Two clocks per stream, never one
Each stream carries when a delivery last arrived, empty or not, and when a delivery carrying at least one row last arrived. The first is liveness and is not, on its own, evidence of anything about your estate; the second is what freshness is computed from. A re-sent batch is folded rather than double-counted, and it still moves the liveness clock, because a retry is proof the exporter ran. - 03
The detectors run over what has landed
The four operator-fed detectors run in the order of their signal rather than of their convenience: the vendor bill first, because it is ground truth for what was actually spent and can find usage that left no network, IAM or endpoint trace at all; then direct egress to a model API; then long-lived and unattributed provider keys; then coding agents on workstations. The fifth needs no export at all and reads the gateway’s own tables. - 04
Each detection folds into the condition it belongs to
A finding is identified by its source and a stable dedup key, and that pair is the identity of a condition rather than of an observation. Re-scanning against a fresh export advances the counters on the finding that already exists and leaves whoever is triaging it alone. A condition that had been resolved and has come back reopens that finding, because that is news. - 05
Coverage is computed and attached to every clean result
The findings list, the scan response, the coverage route and the dashboard summary all carry the same report, attached where it is built rather than by asking four surfaces to remember. A caller cannot read a clean result without also being handed the evidence that result rests on. - 06
Feed health can take a reassuring verdict away, and can never hand one back
Pull-connector runs, push-delivery outcomes and the liveness of the ingest credential a stream arrives through are joined into the coverage report. Where any of them says collection has stopped, a fresh or empty source is demoted to feed_failing. A verdict that is already unreassuring is left exactly as it is: stale and never-connected are older and larger facts than a connector that broke this morning. - 07
Auto-clearing is refused unless four things hold at once
A finding is resolved by absence only when the run completed, the source is one where absence is evidence, the prior-state snapshot was not truncated, and the detection side saw its whole population. A wrongly resolved finding is worse than a stale one: it is a condition that was true, is still true, and is now filed as handled.
Liveness and observation, and why an empty delivery is not no delivery
Coverage records two facts per source — when evidence last arrived, and how long that source may plausibly go quiet — and computes one of five states from them. Only connected_fresh entitles a surface to render an unqualified all-clear. The other four each describe a different thing being wrong, and folding any of them into a neighbour would either produce the lie the model exists to remove or send an operator hunting a connector that is working perfectly.
The distinction the whole model turns on is between a delivery and a row. A delivery proves the connector is alive. A delivery carrying at least one row proves the estate was observed. Those are different facts about the world and they are kept in different columns, because they were once the same column and the consequence was a source held at fresh indefinitely by an exporter delivering nothing. An empty delivery is not meaningless either — a genuinely quiet hour on an egress feed is a real answer, and calling a working exporter silent is how a status surface gets muted, after which it reports nothing at all. So an empty hour keeps the connector alive and moves no observation clock, and connected_empty says both halves out loud.
The bounds are a product judgement rather than a constant somebody picked, and the rule behind every number is roughly two expected cycles, rounded up so a jittery cron does not flap. A vendor master bill and a per-user seat export are published once a calendar month, so the natural gap is already about thirty-one days at its widest; forty-five days is that plus a fortnight, which is the smallest gap that unambiguously means somebody stopped exporting. Egress is six hours rather than twenty-four for one reason: it keeps the detection window inside a working day, and it is the only source that can catch an exfiltration channel while it is still open. One bound across both would either scream about a healthy billing feed every month or stay silent about an egress feed that died at breakfast.
Two smaller decisions are worth knowing because they change what you read. A timestamp that cannot be parsed is reported as stale rather than fresh — corruption makes somebody look, it does not reassure them. And an arrival dated in the future, from a connector with a skewed clock or a restored database, is treated as age zero rather than as a negative age: the alternative arithmetic makes a badly skewed feed look arbitrarily stale and sends an operator hunting a connector that is working.
- connected_fresh
- Evidence arrived inside the bound. This is the only state a clean result can stand on, and the only one that permits an unqualified all-clear anywhere in the product.
- connected_empty
- Deliveries are still arriving and none of them recently carried a row. The connector is alive and the estate has not been observed. Deliberately not folded into either neighbour: fresh would be the lie, stale would send somebody to fix a connector that is running.
- feed_failing
- The evidence is still inside its bound and the thing that fetches or delivers it has stopped. Freshness alone would go on reporting this source as covered for the rest of the bound — up to forty-five days for a seat export — so the earlier of the two true answers is the one reported.
- connected_stale
- Evidence has arrived before and not recently enough. The dangerous state, because everything still looks configured: the connector exists, the credential exists, and the last scan succeeded over evidence that is now weeks old.
- never_connected
- Nothing has ever arrived. A clean result from this source is not a result at all, and the report says so in those terms rather than leaving it to be inferred from a blank timestamp.
stream bound last delivery last row state
egress 6 hours 4 min ago 4 min ago connected_fresh
egress 6 hours 9 min ago 31 hours ago connected_empty
egress 6 hours 3 days ago 3 days ago connected_stale
billing 45 days 6 hours ago 6 hours ago feed_failing *
service_accounts 10 days never never never_connected
* the evidence is well inside its bound; the ingest credential it
arrives on was revoked this morning, so nothing is being collected
and coverage says so today rather than in six weeks' time.Found, fed, knocking: three questions, three surfaces
Was anything found is the findings list. Has this source been fed is the coverage report. Is anything knocking is the connector ledger, and it is a third state rather than a rephrasing of the second. A connector posting Zscaler logs at a Cloudflare mapping produces an empty findings list and an empty arrival record, which is exactly what a connector nobody ever configured produces — and coverage correctly calls that source never_connected, which is true and is not the whole truth. In one of those two situations somebody is actively sending data three times an hour and the fix is a query parameter.
A rejection is deliberately not recorded as an arrival. It would have been trivial to count every delivery as evidence landing and let the freshness clock cover this, and it would have been the same lie one layer further out: the source would read fresh on the strength of deliveries not one row of which could be read. So arrival stays the record of evidence landing, the delivery ledger stays the record of delivery happening, and the two are reported beside each other.
Three kinds of feed can carry evidence to Token Observe and they break differently, so all three are joined into the coverage report where it is built. A push connector is your console or your cron delivering to an ingest receiver, and what breaks is on your side. A pull connector is Token Observe holding a vendor credential you supplied and fetching on a timer — two ship, Cisco Umbrella hourly for egress and GitHub Copilot six-hourly for seat spend, both off until you configure them — and what breaks is a credential that expired, a schema that moved, a rate limit. Each of the two says the same thing about itself, under a heading in its own source that states it: the request shape is written to recorded fixtures and the vendor’s published documentation, and no assertion in the repository has yet met a live tenant. The third kind had no verdict anywhere and is the most used: the ingest credential itself. No shipped pull connector targets billing, service accounts or IDE telemetry, so for those three streams that credential is the only automated feed there is, and when it is revoked or expires every delivery is refused with a 401 that nothing recorded. The console rendered nothing unaccounted for one hour after the deployment’s only credential was revoked. Nothing new had to be stored to fix it; the credential table and the arrival roll-up both already held what was needed. It was a join that was missing.
What deliberately does not demote a source is as considered as what does. Only the states in which Token Observe knows collection has stopped count: a pull run that failed or timed out, a pull connector overdue against its own cadence, a push connector whose deliveries are being refused or which has gone silent, an ingest credential with no live scoped replacement. A connector that has not started — never configured, never run — is reported beside the entry and changes no verdict, because the stream may be fed perfectly well by an operator sending a CSV, and an estate doing the right thing by hand must not be told its evidence is worthless. Rotating a credential the normal way, minting the new one before revoking the old, is a normal Tuesday and never reads as an outage.
- delivering_but_rejected
- Something is posting on schedule, with a valid credential, and none of it is being read. It has fed this source before, so somebody configured it correctly once. This is the state the delivery ledger exists for.
- never_accepted
- The same, and it has never once worked. Almost always a mapping or a stream parameter that was wrong from the first delivery — which, without this surface, is indistinguishable from a connector nobody set up.
- dropping_rows
- Deliveries land and some rows in them do not map. Degraded on purpose: a feed quietly losing a slice of its rows every hour decays into uselessness while every dashboard stays green.
- partial, on a pull run
- The connector reached the vendor, mapped what it got and delivered it, and knows the window was incomplete — a page cap, a truncated report. Reporting that as completed would make an operator believe they are looking at a whole hour of egress when they are looking at some of it.
- overdue, on a pull run
- Runs are succeeding and the last success is older than the connector’s cadence allows, with two cadences of slack and a floor of thirty minutes. A scheduler that has stopped looks exactly like this, which is why it is a state rather than a footnote.
The five detectors, and what each one can actually prove
The vendor master bill runs first because it is ground truth for what was spent, and because no other detector can find usage that left no network, IAM or endpoint trace at all. Vendor lines for a calendar month are compared against the gateway’s own metered spend for the same month, with two tolerances applied together — a gap must exceed both one dollar and five per cent of the larger side — because rounding, currency conversion and mid-month proration make small gaps meaningless. Severity is taken as the worse of a ratio reading and an absolute reading, so a ninety per cent gap on twelve dollars and a six per cent gap on forty thousand are both reportable and neither hides behind the other. Over-metering is reported and capped: it is a price-table problem that should reach an engineer, not the risk register.
That reconciliation refuses to guess a join, and the refusal is the most interesting decision in the detector. Vendor lines are keyed by invoice name and account; metered lines are keyed by the provider name an operator registered, which is free text. Matching them by string would report every provider whose registered name differs from its invoice name as unmetered spend — the most severe finding the product emits — for money that was metered perfectly. So per-provider attribution happens only where you have declared the binding, an account claimed by two provider rows falls back to the month-level aggregate rather than being resolved by list order, and a bound provider under which nothing was metered at all is reported as unverifiable at medium rather than as an unmetered critical, because a renamed provider row is the usual cause and historic traces keep the name they were served under.
Per-user seat spend reads the same kind of evidence one named person at a time, and it is the first radar shape whose subject is an employee, so what travels where is deliberate. The finding detail carries the address, the months, the amounts and the internal user id where one matches, because there is no version of this feature that withholds them — somebody spending four thousand dollars a month outside the gateway is not actionable otherwise. The summary and the dedup key carry no address at all: both leave the process into an alert, and an alert lands in a chat channel with a far wider readership than the console, so the key is the first twelve hex characters of a digest of the address, which is stable, unique per person and meaningless to a recipient. Severity reads the largest single month rather than the total, so a twelve-month export and a one-month export rate the same person the same way.
Egress, keys and IDE telemetry each answer a narrower question. Egress matches destinations against a fixed list of vendor API hostnames by exact match or subdomain, plus a shape match for regional Bedrock endpoints — substring matching would happily accept a hostname ending in a lookalike domain — and flows whose source is the gateway itself are excluded, since those are what the gateway is supposed to produce. The key audit reports three shapes that make a bypass durable: keys past the rotation window, keys the gateway has no record of using, and keys idle long enough to revoke outright, with the key itself as the identity — the provider, the account and a twelve-character digest of the key id, never the raw value, which is masked wherever it is stored — so that one credential ageing through three thresholds advances one finding instead of opening three. IDE telemetry recognises six coding agents by name and flags any whose configured base URL is not this gateway; an unset base URL means the tool falls back to the vendor’s own endpoint, which is the most common bypass there is and the one nobody notices because nothing looks misconfigured.
The fifth source needs no export from you and cannot go stale between them, because it reads the gateway’s own tables. Its strongest finding is the unpriced model: cost is computed at zero when no price row matches, so a model nobody registered is metered at zero dollars while the vendor bills for it in full, and that finding names by key the exact billing-gap findings its months explain rather than merely correlating with them. It is the one case where governed traffic is invisible to governance. The predicate is narrower than zero cost alone, because a response served from the gateway’s own cache is also metered at zero on purpose: requiring input plus output tokens above zero excludes every cache hit without parsing a byte of the payload, and the test suite drives real cached responses through the gateway to assert they never raise a finding. The same source also reports credentials in daily use on agents nobody activated, active agents with no usable credential, and sustained pressure from callers presenting credentials the gateway refuses — a revoked key still arriving every day being a decommissioned integration that did not stop when its access did.
- Blocked is said plainly, where it applies
- A credential presented on a draft or retired agent produced calls the gateway refused. The finding states that the calls were blocked and that it is not evidence of ungoverned traffic, so nobody spends an afternoon hunting a bypass that never happened. What it also states is the question worth asking: Token Observe cannot see where a refused workload went next.
- Noise gates on rejected callers
- Two gates rather than one — at least five attempts, and across at least three distinct hours. A handful of attempts is noise however long it spans, and a thousand rejections inside one minute is a deploy going wrong rather than a channel. Sustained beats loud.
- The credential is never stored, and neither is a digest of it
- Rejections are counted, not captured. A digest would be an offline oracle against a secret that may still be live somewhere else, so the finding carries counts, a reason and the key id only where the credential resolved to one the gateway issued.
- A capped count is reported as a floor
- Sighting writes are capped per hour bucket, because that is the only radar path an unauthenticated caller can reach and every rejected request would otherwise drive a write. A bucket that reaches the cap stops counting, and every finding computed from one says so — the claim is that something is calling which the gateway will not serve, and that is no less true at two thousand attempts than at two hundred thousand.
billing gap > $1 AND > 5% of the larger side
severity: 15/35/60% by ratio, $100/$1k/$10k by amount
seats largest single month: $50 / $250 / $1,000
egress 1 MiB / 50 MiB / 500 MiB, or 100 / 1,000 connections
service keys > 90d medium, > 365d high, > 60d idle low
unknown to the gateway: high, or critical if used
within the last 7 days
ide vendor default or a vendor host: high
some other proxy: medium (a known hop, not an incident)
self unpriced model: 50k / 1M / 10M tokens, cache hits
excluded by requiring input + output tokens > 0
rejected callers: >= 5 attempts across >= 3 hoursHow evidence gets in, and what the credential can do
The connection points inwards, and that is the whole security argument rather than a deployment convenience. Token Observe could have held read access to the finance system, the flow logs and the vendor IAM, and would then be a far more attractive target than the thing it protects. Instead your own scheduled exporter pushes, on a credential Token Observe issued, and that credential may only post evidence rows for the streams it was scoped to. It cannot read findings, run a scan, change a policy, call a model or reach any other surface: no resolver outside the ingest route looks it up. The reach is stated in the response at the moment of minting, because that is when somebody decides where to put it.
Minting an ingest credential is an admin action and revoking one is an operator action, and the asymmetry is deliberate: issuing a standing machine credential into the governance record is an admin decision, and a credential nobody can revoke without finding an admin is a credential that stays live through the incident. Revocation stops the next batch and does not undo the last one — evidence already delivered is kept and findings computed from it stay open — and the response says so rather than leaving it to be discovered.
One vendor forced an unusual credential and the way it was handled is worth reading before an evaluation. Splunk’s built-in webhook alert action posts JSON to a URL you type and offers nowhere to put a header, so the credential has to survive being in a URL. Rather than pretend a URL is a safe place for a secret, Token Observe makes it a narrow, separate, watchable one: it is a different secret from the ingest token, it is never wider than the ingest key it hangs off and narrowing that key narrows it on the next request, it cannot be presented as a bearer token anywhere — not by a rule but by shape, since it resolves only against the delivery-URL table and no other resolver reads that table — it is revocable on its own at operator rank, and its use count and last use are on the status surface. That last one is the compensating control for a secret that cannot be kept secret: you cannot stop it being copied, but you can see it being used. It is shown exactly once, because only its digest is stored.
Every bound at the receiver refuses whole rather than truncating, and the reason is the same rule that governs the coverage model: a silently shortened batch leaves you believing Token Observe holds evidence it does not hold. The one bound that is not all-or-nothing is the mapping tolerance, and it is two-sided on purpose. Refusing fifty thousand good rows because one flow had no destination would make a healthy feed permanently dead. Accepting a batch where half the rows do not map would publish findings from a misread log, because half not mapping is what a wrong mapping looks like rather than what a dirty feed looks like. So a few junk rows are dropped and counted, never silently, and a delivery past five per cent is refused whole with the ratio named.
- Idempotent by window or by content
- An exporter that knows the window it sent names it and that name is the batch’s identity; one that dumps its current export every hour is deduplicated on content. A re-send is folded rather than double-counted, and it still moves the liveness clock, because a retry is proof the exporter ran. It moves the observation clock too when the batch it folded into carried rows, since an unchanged export re-sent is a re-observation reporting the same answer; an empty delivery moves only the first.
- A missing column is a refusal, not a default
- A byte count quietly defaulted to zero produces a row every detector accepts and none ever fires on. Common alternative spellings are accepted; a column that cannot be found is named in the refusal along with the field names the delivery actually carried.
- The backlog is bounded and the refusal names the real cause
- Rows are held until a detection pass consumes them, so an exporter running every five minutes against a radar nobody scans would otherwise fill the database. Fifty unconsumed batches is several days of an hourly connector, so reaching it means detection has stopped rather than that the exporter is too fast, and that is what the message says.
- The ingest door is never licence-gated
- Minting the credential is gated on the push-receivers module; the door a delivery arrives at is not. A delivery already in flight must not be lost because a licence lapsed, and an install with no ingest key cannot receive anything anyway.
records per delivery 10,000 400, nothing stored
bytes per delivery 8 MB 413, nothing stored
deliveries per credential 120 burst 429 with retry-after,
1 per sec nothing decoded
unconsumed batches/stream 50 429 — detection has stopped,
not the connector
records that fail to map 5% dropped and counted; past 5%
the delivery is refused whole
Nothing is ever truncated. Every refusal appears on the connector
status surface with the field names the delivery carried, so a feed
delivering into a void is not mistaken for a source nobody set up.What a finding is, who may read it, and when it is allowed to close
A finding is a lead to investigate rather than proof, and every one carries the evidence it was raised from. Its identity is the source plus a stable dedup key, and that pair names a condition rather than an observation, so a re-scan over a fresh export advances the existing finding instead of minting a second one beside it. Each pass is a set difference against what was true last time, which produces the transitions an operator actually acts on: new, recurring, escalated, de-escalated, regressed and cleared. Recurring is the overwhelming majority and is silent by design.
Closing a finding by absence is the operation that has to be hardest, because a wrongly resolved finding is worse than a stale one — it is a condition that was true, is still true, and is now filed as handled. Four things must hold. The run for that source reported completed, since a failed or timed-out run produced a partial picture, and “I did not see it” is not “it is not there”. The source is one where absence is evidence, which is only the self source: it reads the gateway’s own tables and is authoritative, while a scan of an operator-supplied export sees exactly the rows somebody posted, and an export that omits last month’s shadow key has not remediated it. The source actually ran in this pass, so that switching a detector off cannot quietly close everything it ever reported. And the detection side saw its whole population, because a condition that fell past a row ceiling is missing from the observed set for exactly the same reason a remediated one is.
Read access is split by what the payload carries rather than by importance. The findings list is auditor rank, because a finding carries workstation hostnames, staff usernames, service-account identifiers and, on a seat row, an employee’s email address — and that rank is a deliberate raise to match a promise the threat model had been making to buyers while the code said otherwise. The coverage report is viewer rank, carrying a typed error code and never a run’s message text, because it is the payload a console has to render beside nothing found, and a coverage report only privileged users can read is a coverage report the all-clear gets rendered without. Run history is auditor, because a parse failure quotes the row value that failed and a row value can be a person’s address; pull-connector runs are auditor for the neighbouring reason, that a vendor’s own text travels in a run’s message. The connector ledger is operator, because it carries a rejection reason with quoted values struck out. Rank is not the only gate on any of them: every radar surface also demands an organisation-wide evidence scope, so an account scoped to a single team is refused outright rather than served a partial estate.
Unattended scanning is off by default, and the schedule lives in the database rather than in a timer. The obvious implementation resets its clock on every restart, so a process redeployed every forty minutes with an hourly interval never scans at all and nothing anywhere reports it — the schedule looks enabled, the logs look healthy, and the radar is simply never asked a question. Instead the next due time is a column written when a run finishes, so a restart changes nothing. A wedged detector is closed as timed out against a per-source ceiling and cannot stop the other four; a run abandoned by a dead process is reaped rather than waited on forever; a source that keeps failing backs off exponentially with jitter, capped so that it still retries roughly hourly and a fleet that failed together does not synchronise its retries.
- Zero new findings is never a green banner on its own
- A completed scan announces its count inside the coverage verdict when there is one, so the two cannot be read apart, and the green branch is reachable only when every source is fresh. Zero new findings in a green banner is the same lie as an empty list, and worse for arriving in response to somebody asking.
- Unknown coverage is unknown, never healthy
- A console that receives no coverage field — an older control plane, a proxy that rewrote the envelope — withholds the all-clear rather than granting it, and any state string it does not recognise counts as silent. A newer server must not be able to teach an older console to render green by renaming a state.
- The claim travels as a sentence
- Each source carries the server’s own sentence about what its evidence supports, and the console renders it verbatim rather than reconstructing a weaker one from the numbers beside it. The API defends the sentence it sent.
- A truncated read disables clearing rather than resolving quietly
- Every read that grows with traffic is bounded in SQL, and hitting a ceiling is reported rather than absorbed. Exactly at the ceiling counts as truncated: telling exactly N from N and more costs another query, and the only price of assuming the worst is a pass that declines to auto-clear.
What this does not do
Stated here rather than discovered during an evaluation. Every line below closes off a reasonable assumption a reader would otherwise carry into a proof of concept.
- No live access to your billing system, your network taps, your cloud IAM or your developer workstations is required, and a default install holds none. The two optional pull connectors are the one exception, and only once you have configured one with a vendor credential you supply. Four of the five sources are otherwise pure functions of rows you export, so a finding from them is only as current as the export it was computed from.
- The radar detects and reports; it blocks nothing. It cannot stop a workstation talking to a vendor API, and it cannot tell you where a workload the gateway refused went next — that is the question the finding tells you to ask.
- Egress detection matches a fixed list of vendor API hostnames plus a shape match for regional Bedrock endpoints, and IDE detection recognises six named coding agents. A model endpoint or an agent CLI outside those lists is not detected, and adding one is a code change rather than a setting.
- Per-provider billing attribution exists only where you have declared which invoice accounts belong to which provider row. Nothing is name-matched, fuzzy-matched or attributed by elimination, so an estate that has declared no bindings gets the month-level reconciliation and nothing finer.
- A silent endpoint seat cannot be told from a person on leave. The finding says so in as many words, because an operator who reads it as proof of tampering will chase the wrong thing and one who reads every instance as leave will eventually miss the real one.
- Neither shipped pull connector has been run against a live vendor tenant. Cisco Umbrella’s query parameters, pagination and field names, and GitHub Copilot’s endpoint path and field names, are written to recorded-shape fixtures and each vendor’s published documentation, and each connector says so in a section of its own source headed exactly that. A field a vendor has since renamed lands as a typed schema error on the connector status surface rather than as silence, which is the loudest thing this design can honestly do about it.
If one of those limits is the thing that decides it for you, say so and you will get a straight answer about whether it is on the roadmap or out of scope.
Talk it throughWhat this leans on
Spend controls
Hard USD ceilings, per-minute rate limits and a kill switch, all decided before the request leaves your network.
Agent registry
One record per agent, and it is the record the gateway enforces against.
Endpoint seats
Policy enforced inside each vendor’s own administrator hook, decided offline against a signed bundle, because a hook that phones home fails open.
How do you tell a dead feed from a clean estate?
By recording two facts per source and reporting them beside every clean result. Coverage stores when a delivery last arrived, which proves the connector is alive, and when a delivery carrying at least one row last arrived, which is what freshness is computed from. Those produce five states, and only connected_fresh entitles a console to render an unqualified all-clear; the others name the source, say when it last delivered, and say what its silence means. The report is attached to the findings list, the scan response, the coverage route and the dashboard summary where it is built, so no surface can quietly decide an empty list is good news.
What happens when a connector delivers an empty batch every hour?
The source moves to connected_empty, which says both halves out loud: the connector is alive and the estate has not been observed. That state exists because it was once wrong. Arrival used to mean a batch arriving, so an exporter delivering an empty page every hour held its source at fresh indefinitely, with a row count of zero stored beside the claim where nothing read it. A wrong window, an empty page or a permission downgraded to one that returns nothing are the commonest ways a real exporter fails, and each is worse than no feed at all, because coverage was affirmatively asserting freshness over the top.
Does Token Observe need access to our finance system or our firewall?
No, and the direction of the connection is the security argument rather than a deployment convenience. Your own exporter pushes to a receiver on a credential Token Observe issued, and that credential may only post evidence rows for the streams it was scoped to — it cannot read findings, run a scan, change a policy or reach any other surface. Two optional pull connectors do hold a vendor credential you supply, Cisco Umbrella for egress and GitHub Copilot for seat spend, and both are off unless configured. The alternative was considered and rejected: a governance gateway holding read access to the finance system and the flow logs is a more attractive target than the thing it protects.
How quickly do you notice when an ingest credential is revoked?
The same day, rather than at the end of the freshness bound. Coverage alone cannot answer this in time, because bounds are measured in cycles of the export and a vendor bill is allowed forty-five days of silence, so a credential revoked this morning would leave its source reading fresh until September. So the credential registry, the pull-connector run history and the push-delivery ledger are joined into the coverage report, and a source whose feed has demonstrably stopped is demoted to feed_failing while its evidence is still inside its bound. Rotating a credential normally — mint the new one, then revoke the old — never reads as an outage.
Can a finding be closed automatically when the problem is fixed?
Only for the source that reads the gateway’s own tables, and only when four things hold at once: the run completed, the prior-state snapshot was not truncated, the source actually ran in that pass, and the detection side saw its whole population. The four operator-fed sources never clear by absence, because a scan sees exactly the rows somebody posted and an export that omits last month’s shadow key has not remediated anything. The reasoning is that a wrongly resolved finding is worse than a stale one: it is a condition that was true, is still true, and is now filed as handled.
Who in our organisation can read radar findings?
Auditor rank and above for the findings themselves, because a finding carries workstation hostnames, staff usernames, service-account identifiers and, on a seat row, an employee’s email address. That is a deliberate raise: the threat model had promised buyers auditor-and-above while the code said viewer, and the promise was the thing people had acted on. The coverage report sits at viewer rank on purpose, carrying typed error codes and never a run’s message text, because it is what a console must render beside nothing found — a coverage report only privileged users can read is a coverage report the all-clear gets rendered without. Rank is not the whole answer either way: every radar surface also requires an organisation-wide evidence scope, so an auditor scoped to one team is refused rather than shown that team’s slice.
Prefer to ask a person? Write to us →
Bring us the agent you are least comfortable with.
Write to hello@tenhaw.com with what your agents do, which providers they call and what would have to be true for you to put something in front of them. James Rooney replies. You will get a straight answer about whether Token Observe fits, including when it does not.
no form · no qualification step · no sales desk · the other three ways in