Vulnerability Matching
When you upload an SBOM (Software Bill of Materials) to DevGuard, the system automatically scans all components to identify known vulnerabilities. This document explains how DevGuard’s vulnerability matching engine works.
Overview
The matching process consists of several steps:
- SBOM Parsing: The uploaded SBOM (CycloneDX format) is parsed into a graph structure
- Component Extraction: Each component with a Package URL (PURL) is extracted
- Database Query: Components are matched against DevGuard’s vulnerability database
- Version Comparison: Version ranges are evaluated to determine if a component is affected
- Result Aggregation: Matched vulnerabilities are collected and deduplicated
The Matching Pipeline
Step 1: SBOM Scanner
The sbomScanner iterates through all components in the SBOM graph. For each component that has a valid Package URL (PURL), it queries the vulnerability database:
SBOM Graph → Component Extraction → PURL Parsing → Vulnerability LookupComponents without a PURL are skipped since they cannot be matched against the database.
Step 2: PURL Parsing and Normalization
DevGuard uses the Package URL (PURL) standard for component identification. When a PURL is parsed, DevGuard creates a match context that determines how to search the database:
| Version Type | Description | Example |
|---|---|---|
| Semantic Version | Standard semver format | 1.2.3, 2.0.0-beta.1 |
| Exact Version | Non-semver strings matched exactly | 20240101, custom-build |
| Ecosystem Specific | Platform-specific versioning (deb, rpm, apk) | 2.47.3-0+deb13u1, 1.2.3-r4 |
| Empty Version | No version specified | Components without version info |
The PURL is split into:
- Search PURL: The package identifier without version (used for database lookup)
- Normalized Version: The version string prepared for comparison
- Qualifiers: Additional metadata like architecture, distro, or epoch
Step 3: Database Query
DevGuard maintains an affected_components table that stores vulnerability data from multiple sources. Each affected component record contains:
- PURL without version: Package identifier for lookup
- Ecosystem: The package ecosystem (e.g.,
Debian:12,Alpine:v3.22) - Type: Package type (e.g.,
golang,npm,deb,apk) - Version ranges:
semver_introduced,semver_fixed,version_introduced,version_fixed - CVE associations: Links to CVE records with exploit and relationship data
The query is built dynamically based on the version interpretation type:
Semantic Version Matching
For semver packages, DevGuard uses range-based queries in the database:
WHERE purl = 'pkg:npm/lodash'
AND (
version = '4.17.10'
OR (semver_introduced IS NULL AND semver_fixed > '4.17.10')
OR (semver_introduced <= '4.17.10' AND semver_fixed IS NULL)
OR (semver_introduced <= '4.17.10' AND semver_fixed > '4.17.10')
)This matches if:
- The exact version is listed as affected
- The version is below a fixed version (with no introduced version)
- The version is at or above the introduced version (with no fixed version)
- The version falls within the introduced-to-fixed range
Ecosystem-Specific Matching
For platform packages (Debian, Alpine, RPM), version comparison happens in Go code after the initial database query, using ecosystem-specific version parsers:
- Debian (deb): Uses epoch-aware comparison (e.g.,
1:2.47.3-0+deb13u1) - Alpine (apk): Supports Alpine-specific version suffixes (e.g.,
1.2.3-r4) - RPM: Handles RPM version-release format
Ecosystem-specific versions often cannot be directly compared using semver rules. DevGuard uses specialized parsers for each ecosystem to ensure accurate version matching.
Step 4: Qualifier and Ecosystem Filtering
When a distro qualifier is present in the PURL, DevGuard filters results to match the specific distribution:
pkg:deb/debian/git@2.47.3?distro=debian-13.2This is matched against affected components with ecosystem = 'Debian:13'.
For Alpine:
pkg:apk/alpine/curl@8.14.1-r2?distro=3.22.2This matches ecosystem = 'Alpine:v3.22'.
Step 5: Version Range Verification
For ecosystem-specific packages, after retrieving potential matches from the database, DevGuard performs precise version checking using the CheckVersion function:
match, err := CheckVersion(
component.Version, // exact version if any
component.VersionIntroduced, // first affected version
component.VersionFixed, // first fixed version
lookingForVersion, // version from SBOM
component.Type, // "deb", "rpm", or "apk"
)A component is considered affected if:
- It matches an exact affected version, OR
- Its version is >=
introducedAND <fixed
Step 6: CVE Deduplication
Vulnerabilities may be reported under multiple CVE IDs (aliases). DevGuard automatically deduplicates these:
- If CVE-A aliases CVE-B (unidirectional), keep CVE-A
- If CVE-A and CVE-B alias each other (bidirectional), keep the lexicographically smaller one
This prevents the same vulnerability from being counted multiple times.
Example Matching Flow
Consider an SBOM containing:
pkg:npm/lodash@4.17.10- Parse PURL: Extract
npm,lodash, version4.17.10 - Normalize Version: Convert to semver
4.17.10 - Query Database: Find affected components for
pkg:npm/lodash - Match Ranges: Check if
4.17.10falls within any vulnerable range - Return CVEs: Return
CVE-2019-10744,CVE-2020-8203, etc.
For a Debian package:
pkg:deb/debian/openssl@3.0.13-1~deb12u1?distro=debian-12- Parse PURL: Extract
deb,debian,openssl, version3.0.13-1~deb12u1 - Interpret Version: Ecosystem-specific (Debian)
- Query Database: Find affected components for
pkg:deb/debian/opensslwithecosystem LIKE 'Debian:12%' - Fetch Candidates: Get all potentially affected entries
- Version Check: Use Debian version parser to compare against each candidate
- Return CVEs: Return matching vulnerabilities