CVE enrichment for cloud workloads
om enrich cve writes a Finding for each CVE that matches inventory
already stored on a workload. Collectors and plugins set that inventory. Enrichment does not
read a host filesystem or a container image.
./om enrich cve
./om enrich cve --cve CVE-2021-44228 --internet-only=false
./om enrich cve --catalog examples/cve-catalog.json --internet-only defaults to true. Targets are workloads reachable from
internet:global by REACHABLE edges only, depth under 8. Set the
flag to false to consider every workload. --cve limits which ids are considered.
Each id still has to match that workload’s inventory. Ids are trimmed and uppercased, and
must start with CVE-. Omitting --cve does not invent a default id.
Inventory
On a Workload, enrichment reads:
packages— a string or a list of strings. Each entry is a CPE 2.3 name (cpe:2.3:a:apache:log4j:2.14.1:*:*:*:*:*:*:*) or a versioned package URL (pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1). Other strings are skipped.image— one image reference.images— a list of image references. Duplicates ofimageare ignored.
The demo graph stores cpe:2.3:a:apache:log4j:2.14.1:*:*:*:*:*:*:* on
web-1 and 2.17.1 on worker-1. After
om enrich cve, CVE-2021-44228 is attached to web-1 only.
frontend stores image: nginx:1.25.3, which matches only when a
catalog lists that exact ref.
om scan k8s writes images from init containers and app containers.
om scan aws writes image as the AMI id when the instance has one.
A plugin can set packages directly. See
Plugins.
NVD
With no catalog, enrichment calls the
NVD API 2.0
(https://services.nvd.nist.gov/rest/json/cves/2.0). NVD matches CPE 2.3 package
names. Package URLs and image refs are not sent to NVD. A workload with no concrete CPE
version gets no CVE finding from this path.
For each concrete CPE, the command requests virtualMatchString set to that CPE,
then keeps a CVE only when a vulnerable configuration criterion has the same part, vendor,
and product and the package version falls in range. A criterion whose vendor, product, or
part is * or - does not match. Negated configuration nodes and
criteria with vulnerable: false are ignored. --cve looks those ids
up with cveId and applies the same check. An id NVD does not know fails the
command. A known id that does not match inventory is skipped.
Versions are dotted integers. 2.0 and 2.0.0 are equal. A version
that is not dotted integers matches only an exact criterion string, not a range. An
unbounded criterion matches nothing.
Set NVD_API_KEY when you enrich more than a handful of packages. The key is sent
as the apiKey header. Without it, NVD’s public rate limit applies. Each request
times out after 20 seconds. Lookups and searches are cached for the process. A search that
reports more than 200 CVEs for one CPE fails the command. Any status other than 200 fails
the command. Findings already written in that run stay.
Catalog
--catalog or OM_CVE_CATALOG points at a JSON file and replaces NVD
for that run. The flag wins when both are set. The file is the whole source: CPE names,
package URLs, and image refs are matched locally, and CVSS is taken from the file.
{
"cves": [
{
"id": "CVE-2021-44228",
"description": "Apache Log4j2 2.0 through 2.14.1 JNDI features do not protect against attacker-controlled LDAP.",
"cvss_score": 10,
"packages": [
{
"cpe": "cpe:2.3:a:apache:log4j:*:*:*:*:*:*:*:*",
"version_start_including": "2.0",
"version_end_including": "2.14.1"
},
{
"ecosystem": "maven",
"namespace": "org.apache.logging.log4j",
"name": "log4j-core",
"version_start_including": "2.0",
"version_end_including": "2.14.1"
}
]
}
]
}
A package entry sets cpe or ecosystem plus name, not
both. A CPE whose version is * needs a version field or a
version_start_including, version_start_excluding,
version_end_including, or version_end_excluding bound. Package URL
rows match ecosystem, namespace, and name, then the same version rules. Image rows match the
full reference string. examples/cve-catalog.json in the core repo matches the
demo log4j CPE and the log4j-core package URL.
cvss_score from 0 to 10 drives severity. A score of 0 uses severity
when that label is set, otherwise info. A --cve id that is not in
the file fails the command.
What is written
For each matching CVE on a target workload, the command upserts one finding and a
VIOLATES edge. The finding id is
finding:{cve-id-lower}:{workload-id}, so a second run overwrites the same
row. The edge property relationship is affects. A later run does
not delete a CVE finding whose package has since left the workload. Enrichment does not
write attack-path findings. Run om rules run afterward so a new CVE on a
workload that can reach a datastore is paired with that datastore. See
Attack paths.
finding_typeiscve.matched_identifieris the package string or image ref that selected the CVE.cve_id,title, anddescriptioncome from NVD, or from the catalog. The NVD title is the English description, collapsed to a single line and cut at 120 characters. If there is no English description, the title is the CVE id. A catalog title is used as written; otherwise the description is cut the same way.cvss_scoreis the first NVD base score found, preferring CVSS v3.1, then v3.0, then v2, or the catalog’scvss_score.severityandnormalized_scorecome from the table below when a score is present. A score of 0 uses the NVD or catalog label instead (moderatebecomesmedium).
| CVSS base score | Severity | Normalized score |
|---|---|---|
| 9.0–10 | critical | 95 |
| 7.0–8.9 | high | 75 |
| 4.0–6.9 | medium | 50 |
| above 0 and below 4 | low | 25 |
| 0 or missing | info, unless the NVD label maps higher | 10 for info |
CVE scores are not boosted by attack-path context. A critical CVE on an isolated workload
and the same CVE on a path to a datastore store the same normalized_score.
CSPM rules are the ones that add graph points. See
Prioritization.
When the workload property public_ip is true, or is a non-empty string, or
public_ip_address is a non-empty string, the command also upserts
finding:internet-exposed:{workload-id}. That finding has
finding_type: exposure, severity high, and normalized score 75. It does not
require package inventory and it does not call NVD. AWS stores public_ip as a
bool. Azure and GCP store the address string, which also counts.
The CLI prints Enrichment complete: N findings created, M updated. This version
counts every successful attach as created. The stable id still means the row is updated
rather than duplicated.
Copyright © 2026 OpenSourceOM. Licensed under Apache-2.0.