August 20, 20263 min read

Small ranges, big claims

Recon reports coverage in three states, not two. The interesting part is what decides the state: not whether the request succeeded, but what the answer can support.

Scan grades a URL and hands back a letter. That works because every check it runs is a check it controls. The header is set or it isn’t. If something fails, the failure is the finding.

Recon doesn’t work like that. It maps a domain’s attack surface by asking external services what they know, and those services are not always willing to answer. When one drops out I don’t have a finding. I have less of the map.

The subdomain section pulls from two certificate transparency sources, crt.sh and Certspotter. It reports its coverage as one of three states: comprehensive, partial, or unavailable. That type existed before any of the CT code did, in the first commit of the package, because a prototype had already produced the failure it guards against. That version found twenty of a domain’s several hundred subdomains and presented them as the set.

The obvious way to derive three states is to count how many sources answered. Both, one, neither. That is not what the code does, and the reason is the part worth writing down.

If crt.sh answers, coverage is comprehensive, whether or not Certspotter did. If only Certspotter answers, coverage is partial, permanently. There is no result Certspotter can return that makes it comprehensive, because its free tier under-counts large domains and I know that independently of any particular response. So the state isn’t a tally of successful requests. It’s a claim about what the answer can support. One source is treated as sufficient evidence for a total, the other is not, and the code encodes that asymmetry directly rather than pretending the two are interchangeable because both returned 200.

The third state works on the same logic in the other direction. Unavailable is keyed on whether the sources succeeded, never on how many names came back. A source that returns successfully with nothing in it produces comprehensive with a count of zero, and the tool says none found. A source that times out produces unavailable, and the tool says it couldn’t look. Those are different sentences because they are different facts, and collapsing them into one empty list is the tool asserting something it has no basis for.

That third state was the one I resisted. It renders as an absence where a result should be and it looks like the tool is broken. It took a while to accept that this is correct, because in that situation the tool has in fact failed, and the honest render of a failed check is not a reassuring empty list. The instinct is that a security tool should reduce uncertainty, so an explicit “I don’t know” reads like a failure of the product. But the alternative isn’t less uncertainty. It’s the same uncertainty with a checkmark on top, which is worse, because now the person reading has a reason to stop looking.

The uncomfortable part is that this model covers one section out of four. Registration has a single source and degrades through a plain ok-or-error flag. Typosquat rows carry an undetermined state per row. Apex DNS has no coverage concept at all: if the lookup fails, the records collapse to empty and render as a dash, which is the exact conflation the subdomain section exists to avoid.

Some of that is defensible. A middle state is meaningless when there’s only one door, so two states for registration is the right shape rather than a missing third. The DNS case is not defensible on those grounds. It has one source, so it doesn’t need partial, but it does need the difference between no records and no answer, and it doesn’t have it.

What generalises is narrower than “show your uncertainty.” Every check covers some range, and the claim it makes has to be scoped to that range and no wider. When the range shrinks, the claim shrinks with it. When the claim is a binary and can’t shrink, the only honest move left is to withhold it. What doesn’t generalise, apparently, is remembering to do this in all four places.

The state a check reports should describe what its evidence supports, not whether its request succeeded. When you can’t check, say so.

Interested in how this maps to your work?

If this kind of thinking fits a problem you're facing, let's talk it through.

Start a conversation EAT (UTC+3) · Typically responds within 24 hours