A scanner tells you your web server has 110 critical vulnerabilities. It has none. Almost every false positive in external vulnerability scanning traces to the same design decision — and the same decision makes scanners miss real flaws in the other direction.
This matters more than it sounds, because both failure directions have the same consequence: the report stops being read. A page of criticals you do not have teaches people to dismiss the tool, and once dismissed, the real finding goes with it.
Where false positives actually come from: text search instead of identifier matching
Vulnerability databases can be queried in two very different ways.
The easy way is full-text search. Ask for every advisory whose description mentions nginx and you get a large result set in one request. It is simple to build and it looks like it works.
It does not, because advisory descriptions mention products for many reasons. A result set for "nginx" contains flaws in nginx itself, but also flaws in third-party nginx modules, flaws in entirely separate products that happen to run behind nginx, and advisories whose text says something like "similar to the earlier nginx issue". Attribute all of those to the customer's web server and you produce a page of critical findings they do not have.
The correct approach uses the structured product identifier that advisories carry — in the US National Vulnerability Database this is CPE, a formal notation naming vendor, product and version, paired with explicit start and end bounds in the record's configuration. A flaw is attributed to nginx only when the record itself names nginx in that structured field, not when the prose happens to mention it.
The vendor half matters as much as the product half. This is the detail that catches people who have got as far as CPE matching. Product names are not unique across vendors: http_server belongs to Apache, to Oracle and to IBM. Match on the product component alone and you will file Oracle HTTP Server's version ranges under Apache's httpd, and report vulnerabilities that belong to software the customer does not run.
The failure nobody talks about: false clean
Text search has a second flaw, and it is the more dangerous one.
It also misses vulnerabilities. Some advisories never mention the affected product by name in their description, because the flaw is in a protocol or a shared component and the text describes it at that level. The HTTP/2 "Rapid Reset" vulnerability is the standard example: it affects the HTTP/2 implementations in nginx, Apache and most others, and a keyword search for "nginx" does not find it.
So the same broken technique produces both invented findings and missing ones. A customer can see a clean report for a server that has genuinely exploitable flaws — and a clean report is far worse than a noisy one, because it ends the investigation. Nobody looks for a second opinion after being told everything is fine.
Version matching, where the remaining errors live
Even with correct product identification, two version problems produce misleading results.
Distribution back-ports. Linux distributions routinely apply a security fix to an older version without changing its version number — Debian and Ubuntu maintain patched builds of, say, 2.4.59 that contain fixes released upstream in later versions. A scanner comparing version numbers sees an old number and reports the flaw. The software is patched. This is a genuine limit of any external scan, and the honest response is to say the match is unconfirmed rather than to assert exposure.
Advisories with no usable version bounds. Some records claim to affect every version ever released, usually because nobody filled the field in properly. Treating that as a claim means flagging every user of that software forever. Treating it as incomplete data means skipping it — trading a little coverage for a great deal of precision, which is the right trade, provided the report says the record was skipped.
The problem underneath all of this: the data is thinner than it was
Correct matching depends on the structured fields being filled in, and since early 2024 they frequently are not. The US National Vulnerability Database, which does the enrichment work, fell badly behind on adding CPE data to new records and has not fully recovered. A record with no structured product identifier cannot be matched correctly by anyone.
The honest consequences are worth stating. A scanner that only reads that one source will silently miss recent vulnerabilities whose records were never enriched. A scanner that responds by falling back to text search reintroduces every problem described above. The defensible answer is to read more than one source — the CVE Program's own records, ecosystem advisory databases such as OSV and the GitHub Advisory Database, and in Europe ENISA's EU Vulnerability Database — and to be explicit about which vulnerabilities could not be assessed because no usable identifier exists.
The version you cannot see
One more, specific to external scanning. Many servers are configured not to reveal their version — server_tokens off in nginx and its equivalents elsewhere. That is sensible hardening.
It also means a version-less component matches zero advisories and reads as clean. That is the silent-green problem: the scanner has learned nothing and reported success.
The correct behaviour is to report the component as identified but unverified, and count it as unchecked rather than passed. If your scanner reports a hardened server as clean without mentioning that it could not establish a version, you have found something worth asking about.
What to ask a scanner vendor
Four questions that separate careful tools from quick ones:
- Do you match on structured product identifiers, including the vendor, or on advisory text?
- What do you report when you cannot establish a version — clean, or unverified?
- What happens when a data source is unavailable? Does the report say "not checked", or does it show no findings?
- How do you handle distribution back-ports, and does the finding say so?
The pattern behind all four is the same: a good scanner distinguishes "I checked and found nothing" from "I could not check". A tool that collapses those two into a single green result will eventually reassure you about something that is not true.
Why do vulnerability scanners report false positives?
Vulnerability scanners report false positives most commonly because they search advisory text rather than matching structured product identifiers. Searching for a product name returns flaws in that product, but also flaws in its plugins, in unrelated products that merely mention it, and in advisories that reference it in passing. Attributing all of those to the customer's software produces a page of critical findings they do not have. Correct matching uses the formal vendor and product fields inside each advisory record instead.
Can a vulnerability scanner miss real vulnerabilities?
Yes, and a missed vulnerability is the more dangerous failure of the two. A scanner that searches advisory text will miss flaws whose descriptions never name the affected product — common when the vulnerability is in a protocol or shared component, as with the HTTP/2 Rapid Reset flaw that affects nginx without the write-up using the word. A clean report ends the investigation, so a false clean is worse than a noisy report: nobody seeks a second opinion after being told everything is fine.
Why does my scanner report a vulnerability my distribution already patched?
Because Linux distributions routinely back-port security fixes to an older version without changing its version number, so a patched Debian or Ubuntu build still reports the old number. A scanner comparing version numbers sees an old number and reports the flaw. This is a real limit of any external scan, and the honest response is for the finding to state that the match is unconfirmed and may already be patched, rather than asserting that you are exposed.
What should a scanner do when it cannot determine a version?
Report the component as identified but unverified, and count it as unchecked rather than passed. Many servers are deliberately configured not to reveal their version, which is sensible hardening — but it means a version-less component matches zero advisories and reads as clean. If a scanner reports a hardened server as clean without mentioning that it could not establish a version, it is presenting a lack of information as a good result.
Where VulnControl fits
VulnControl harvests version ranges only from records whose structured vendor and product fields match the software it identified, and both failure directions described here are things it got wrong once and fixed: an early version produced 110 false criticals against a single healthy nginx, and a later one reported a server clean while an identical one flagged real flaws. Both are now covered by tests that run on every build. A component whose version cannot be established is reported as unverified, never as clean. The technical description covers the matching in detail.