RESEARCH // OPEN SOURCE // a-failed-lookup-is-not-a-clean-verdict
A Failed Lookup Is Not a Clean Verdict
The defect
A threat scanner calls twenty external providers. Some of those calls fail: a rate limit, an expired key, a timeout, a provider that changed its response shape overnight. The question every scanner has to answer is what it reports when a call does not come back.
The wrong answer is easy to write and almost impossible to see in review:
catch (err) {
return { verdict: "Clean" };
}
That code is not error handling. It is fabrication. It manufactures a security finding out of an infrastructure failure, and it renders that finding with the same green badge as a real one.
Twelve times
We have removed this pattern from IntelStack twelve separate times. It appeared in the sandbox pipeline, in the CTI service layer, in a mock fallback file, in seven separate provider clients that returned Clean on any transport failure, and in an AI action that was asked to generate realistic newly discovered indicators and polled them into the analyst's workspace every two minutes.
None of these were written maliciously. Every one of them was written by somebody making a failure path "not crash". That is the whole problem: the fabrication is what a reasonable person writes when the goal is a green build rather than a true answer.
We now run a detector over the diff in CI, because twelve is enough times to stop trusting review.
The four rules
A failed lookup is Unknown, never Clean. The scanner asked and learned nothing. That is a different state from the provider answering and reporting no record, which is different again from having no credential to ask with. IntelStack renders those as Error, Unlisted and Unconfigured, and none of them is green.
Clean is a finding. Green means a provider affirmatively reported the indicator clean. It is a positive claim that carries evidence. Absence is grey, and grey is not a weaker green.
Every score contribution carries the evidence that produced it. A verdict that says 62/100 and nothing else cannot be argued with, which means it cannot be trusted. Ours itemises every point back to the provider that contributed it.
Never report Clean over contrary evidence. This one cost us the most. A lone VirusTotal detection scored 12 points against a 35-point threshold, and the scanner reported the indicator as Clean — technically consistent with the scoring model, and completely wrong. Provider counts now set the floor independently of the score: if any provider reports malicious or suspicious, the verdict is at least Suspicious, whatever the arithmetic says.
Absence has to look like absence
The same rule governs the interface. An empty list and a failed query must never render identically. A datastore that is unreachable has to produce an error naming the failure, because an empty result in that position is a false all-clear — the interface asserting "we have never seen this indicator" when what actually happened is that the database did not answer.
In IntelStack, history writes swallow their errors and history reads throw. That asymmetry is deliberate. Losing a write costs us a record. Silently returning an empty read costs an analyst the truth.
This research will be directly queryable inside the upcoming IntelStack graph workspace.