For engineers
Too many false CVE alarms? Matching firmware versions right
By Manish SharmaPublished 8 min read
Founder and CEO, MAGDOX. Writes on firmware security and device regulation.
False vulnerability alarms waste engineering time and undermine a buyer's trust in your reports. Most false alarms and missed vulnerabilities in firmware reports come from three places: the wrong version, versions compared in the wrong order, and an advisory sentence read the wrong way. FDIE matches components by package URL against OSV ranges and otherwise against NVD CPE ranges, never by parsing advisory prose. It sorts test builds before their release, treats builds from a commit as not comparable with ranges, and records your team's decisions as VEX statements.
Results as of . We keep improving FDIE’s engine for accuracy, so later runs may score differently; this post records what we measured on that date.
In short
- Most false alarms and missed vulnerabilities come from three places: the wrong version, versions compared in the wrong order and advisory text read the wrong way.
- FDIE matches only against machine-readable OSV and NVD CPE ranges, never advisory prose. It sorts test builds before their release and keeps builds from a commit out of range matching.
- Your team records each decision as a VEX statement, which FDIE keeps across data refreshes and exports as CycloneDX, OpenVEX or CSAF 2.0.
Ask a device maker what they think of vulnerability scanners and you will hear the same complaint. The list is long, half of it does not apply, and after a few releases nobody trusts it. False alarms cost engineering days. Missed vulnerabilities cost far more, and they hide in the same places the false alarms come from.
In October 2026 we rebuilt the analysis engine behind FDIE, vulnerability matcher included. We audited it by replaying the inputs of our previous engine's test suite through the new code. Of 3,890 inputs, 3,882 gave the same answer and the other 8 are explained. This post uses the real cases from that audit to show where matching goes wrong and how to check any tool, ours included.
Why do vulnerability scanners raise false alarms?
They come from three places: the wrong version, versions compared in the wrong order, and advisory text read the wrong way.
A fourth case is not a mistake at all. The component and version really are affected, but the vulnerable code cannot be reached or attacked in your product. That is not a matching problem but a decision your team records, and VEX exists to record it.
Start with the right version
Matching can be no better than the version it starts from. So FDIE takes versions from the strongest evidence it has, and shows which evidence that was.
A version read from the package database (opkg, dpkg or apk) is strong. A version string found inside a program is good. A version inferred from a library name or a code signature is weaker. FDIE shows the identification method and a confidence for each component, so your triage can treat them differently.
We measure this against lists we did not write. OpenWrt publishes a manifest of every package in each image. On four OpenWrt images, FDIE found every package at the manifest's version:
| OpenWrt image | Manifest packages found at the same version |
|---|---|
| PowerPC | 144 of 144 |
| MIPS64 | 115 of 115 |
| ARC | 116 of 116 |
| RISC-V 64 | 106 of 106 |
On an earlier set of 17 OpenWrt images, it found 194 of 194 components.

How should a component be matched to a vulnerability?
FDIE matches a component that has a package URL against OSV's affected ranges, and everything else against NVD's CPE ranges. It never decides from an advisory's free text.
A package URL names a component in a way tools agree on, such as an ecosystem, a name and a version. The OSV schema records affected versions as ranges of events: introduced, fixed, last_affected and limit, plus an explicit list of versions. The schema says a version is affected "if it lies within any one of the ranges or is listed in the versions list".
NVD records affected products as CPE names with start and end versions, each inclusive or exclusive. Both are machine-readable, and both can be checked.
| Identifier | Example (name, then the version it carries) | Matched against |
|---|---|---|
| Package URL (purl) | pkg:deb/debian/openssl, version 3.0.11-1 | OSV affected ranges for that ecosystem |
| Package URL (purl) | pkg:generic/busybox, version 1.36.1 | OSV, where a generic record exists |
| CPE name | cpe:2.3:a:openssl:openssl:3.0.11:*:*:*:*:*:*:* | NVD configurations with start and end versions |
| CPE name | cpe:2.3:a:busybox:busybox:1.36.1:*:*:*:*:*:*:* | NVD configurations |
A component that carries neither has nothing a range can be checked against, which is why identification comes first.
Some matchers add a fallback for advisories without that data. They parse descriptions for phrases such as "before X", "up to X" or "A through B". Our previous engine had such fallbacks, and guards against misreading them. The new matcher has none, on purpose, and the next section is why.
Why is advisory text a trap?
A sentence about one part of a product can read like a sentence about the whole product. A matcher that parses prose will eventually get one wrong in the dangerous direction.
CVE-2025-9230, OpenSSL. The OpenSSL advisory of 30 September 2025 says the FIPS modules in 3.5, 3.4, 3.3, 3.2, 3.1 and 3.0 "are not affected by this issue". It then gives the reason: the CMS code is "outside the OpenSSL FIPS module boundary". The next line lists OpenSSL 3.5, 3.4, 3.3, 3.2, 3.0, 1.1.1 and 1.0.2 as vulnerable, fixed in 3.5.4 and 3.0.18 among others.
A text guard in our previous engine read the first sentence as being about the library, and dropped the CVE for OpenSSL 3.5.0 and 3.0.1. That is a missed vulnerability: an out-of-bounds read and write that OpenSSL rates moderate, in a library many firmware images carry.
CVE-2023-6237, OpenSSL. The advisory of 15 January 2024 says "The OpenSSL 3.0 and 3.1 FIPS providers are affected". The same kind of guard read that as naming the affected branches, and dropped the CVE for 0.9.8 and 1.1.1.
The outcome happened to be right, but for the wrong reason. The advisory lists 3.0.0 to 3.0.12, 3.1.0 to 3.1.4 and 3.2.0 as vulnerable, and says 1.1.1 and 1.0.2 are not affected. The new matcher reaches the same answer from the ranges.
When we replayed the 378 guard calls from the previous engine's tests, 372 gave the same answer as before. The other 6 are these two OpenSSL cases, where the advisories' own text shows the old reading was wrong.
Version order is not string order
A test build comes before its release, and a matcher that does not know the project's naming will sort it after.
dnsmasq publishes test builds before each release. Its test-releases directory, for example, lists 2.87test1 to 2.87test9 ahead of 2.87. A router running dnsmasq 2.86test3 runs code from before 2.86.
If the comparison does not recognise "test" as a pre-release marker, 2.86test3 sorts after 2.86. An advisory whose affected range ends before 2.86 then no longer matches the device. That is a missed vulnerability on one of the most common daemons in routers.
Our previous engine got this right, and the first version of the new matcher did not. The audit replay caught it, and the fix counts "test" as a pre-release word. All 89 version comparisons from the old test suite now agree. When you evaluate a tool, give it 2.86test3 and a range "before 2.86", and see what it says.
Builds from a commit
A program built from a source checkout carries a commit, not a release. Comparing it with release ranges produces noise in both directions.
Go is the clearest case. A Go main module built in place reports its version as "(devel)". FDIE derives Go's own pseudo-version from the commit recorded in the program's build information. The Go Modules Reference describes the form, for example vX.0.0-yyyymmddhhmmss-abcdefabcdef when there is no base version.
FDIE marks such a version as built from a commit and matches it only against explicit lists of affected versions, never against ranges. "A commit from some date" cannot honestly be placed inside "introduced at 0, fixed in 1.4". A dependency pinned at a pseudo-version, which the Go module proxy resolves like any release, is still matched normally.
When the version did not change but the fix is there
Vendors and distributions backport fixes without changing the version string, so FDIE also looks at the code.
Patch checks compare the disassembled code of the release with what a fix looks like. They give the vulnerability a state: fix verified, patch candidate or still vulnerable. So they can find a backported fix even when the version string stayed the same.
Function use, from the call graph, says whether your programs call the vulnerable function at all. Neither of these decides a VEX statement on its own. They are the evidence your engineer reads before deciding.
Recording the decision: VEX
Once your team has decided whether a matched vulnerability affects the product, a VEX statement records that decision. It does so in a form your customers and their tools can read.
The statuses are the same across the standards: not affected, affected, fixed and under investigation, as in CISA's minimum requirements for VEX. CERT-In's Technical Guidelines on SBOM (version 2.0, 9 July 2025) ask suppliers of software to government and public sector organisations to publish VEX with exactly these four. OpenVEX defines five justifications for "not affected", and requires either a justification or an impact statement:
component_not_presentvulnerable_code_not_presentvulnerable_code_not_in_execute_pathvulnerable_code_cannot_be_controlled_by_adversaryinline_mitigations_already_exist
In FDIE, people write VEX statements. The evidence the analysis has is shown next to the vulnerability: how the component was identified, function use and the patch check state. Decisions are kept when the vulnerability data refreshes, and you can export them as CycloneDX, OpenVEX or CSAF 2.0.

A checklist for your own triage
Whatever tool you use, these questions catch most bad matches before they reach a ticket or a customer.
- Where did this version come from: a package database, a string, or a guess from a name?
- Is it a test build, a pre-release or a build from a commit?
- Which range matched, and from which source: an OSV record or an NVD CPE entry?
- Is the CVE in the CISA KEV catalogue, and what is its EPSS probability?
- Does any program in the image call the vulnerable function?
- Is there a backported fix in the code even though the version did not change?
- Is the decision written down as a VEX statement with a justification, so the next release starts from it?
A matcher that answers the first three for every row, and a team that answers the last four, will produce a list people trust again.
Questions
Why does FDIE not match vulnerabilities from the advisory text?
Advisory sentences are written for people and are easy to misread. In the OpenSSL CVE-2025-9230 advisory, a sentence saying the FIPS modules are not affected led an older text rule to drop a vulnerability the library really has. So FDIE decides from machine-readable OSV and CPE ranges only.
How does FDIE compare a version like dnsmasq 2.86test3?
It treats "test" as a pre-release marker, so 2.86test3 sorts before 2.86 and falls inside an advisory range that ends before 2.86, as it should.
Which VEX statuses and formats does FDIE support?
The four standard statuses, not affected, affected, fixed and under investigation, with a justification for not affected. Statements are written by your people and exported as CycloneDX, OpenVEX or CSAF 2.0.
What if a vendor backported a fix without changing the version string?
FDIE's patch checks compare the disassembled code and give the vulnerability a state of fix verified, patch candidate or still vulnerable. So a backport can be found even when the version did not change.
Sources
- OSV schema (affected ranges and events) ossf.github.io
- NVD, Common Platform Enumeration (CPE) nvd.nist.gov
- Package URL (purl) specification github.com
- OpenSSL Security Advisory, 30 September 2025 (CVE-2025-9230) openssl-library.org
- OpenSSL Security Advisory, 15 January 2024 (CVE-2023-6237) openssl-library.org
- dnsmasq test releases thekelleys.org.uk
- Go Modules Reference, pseudo-versions go.dev
- OpenVEX specification github.com
- CISA, Minimum Requirements for Vulnerability Exploitability eXchange (VEX) cisa.gov
- OASIS CSAF 2.0 docs.oasis-open.org
- CERT-In Technical Guidelines on SBOM, QBOM and CBOM, AIBOM and HBOM, version 2.0 (9 July 2025) cert-in.org.in
OpenWrt is a registered trademark of Software Freedom Conservancy; see openwrt.org. Product and project names are trademarks of their owners, used only to identify the public firmware, software and standards discussed. MAGDOX is not affiliated with or endorsed by them.