GUARDIANVIGIL

RESEARCH // OPEN SOURCE // a-failed-lookup-is-not-a-clean-verdict

AUTHOR: DHARRMIN SUTHAR
DATE: AUGUST 24, 2026
Back to Research
Open SourceInfo August 24, 20263 min read

A Failed Lookup Is Not a Clean Verdict

By Dharrmin Suthar (Founder & Lead Security Researcher)GuardianVigil
The most dangerous line in a threat scanner is the catch block. We have found and removed the same defect twelve times in IntelStack: a provider call fails, and the scanner reports the indicator as clean.

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.

IntelStack Threat Graph Integration

This research will be directly queryable inside the upcoming IntelStack graph workspace.

IntelStack Roadmap