Vulnerability monitoring
Off by default. --vuln-enabled on osctrl-tls stores software inventory;
on osctrl-api it downloads advisory data, matches it and serves findings.
Package inventory never leaves the deployment: the only outbound traffic is
the feed downloads below, to URLs the operator controls.
Data flow
Section titled “Data flow”- Apply a
vuln-linux,vuln-darwinorvuln-windowsprofile (GET /api/v1/vulnerabilities/profiles) to an environment’s schedule. The queries run daily in snapshot mode. - osctrl-tls replaces each node’s
node_softwarerows per category on every snapshot and records the OS invuln_node_state. - One osctrl-api replica (SQL lease) syncs the OSV ecosystems the fleet
reports, plus the CISA KEV catalog and, with
--vuln-nvd-enabled, NVD CVEs, every--vuln-sync-hours. It then re-matches nodes whose inventory or advisories changed.
Coverage
Section titled “Coverage”| Inventory | Matched against |
|---|---|
Debian, Ubuntu (incl. non-LTS) deb_packages |
OSV Debian:<major>, Ubuntu:<release>[:LTS], by source package |
RHEL, Rocky, AlmaLinux rpm_packages |
OSV Red Hat:enterprise_linux:<major>::*, Rocky Linux:<major>, AlmaLinux:<major> |
python_packages |
OSV PyPI (PEP 503 names) |
npm_packages (global) |
OSV npm |
Windows programs, chocolatey_packages; macOS apps, homebrew_packages |
NVD CPE (--vuln-nvd-enabled), as possible findings |
Not assessed, and counted as such rather than reported clean:
- Alpine (osquery has no
apk_packagestable); - CentOS, SUSE and other distributions;
- Ubuntu Pro/FIPS and RHEL EUS streams;
- Windows programs, macOS apps, Homebrew and Chocolatey packages while
--vuln-nvd-enabledis off; - packages in an ecosystem whose OSV feed hasn’t synced yet;
- versions without a digit.
Severity
Section titled “Severity”- Source: CVSS v3 (else v4) vectors published in the OSV record, scored locally.
- No vector: many Debian and Ubuntu records publish none, and their
severity is
unknown. - KEV: a finding is flagged when any CVE alias of its advisory is in the CISA catalog.
NVD possible findings
Section titled “NVD possible findings”With --vuln-nvd-enabled, osctrl-api also syncs CVEs from the NVD CVE API
2.0 and matches Windows programs, macOS apps, Homebrew and Chocolatey
packages by product name.
- Names: the installed name and vendor are normalized to CPE spelling.
The vendor is the Windows publisher or the macOS bundle id. The names are
then looked up exactly among the products stored CVEs list
(
vuln_cpe_products). Without a matching vendor, a product is taken only when a single vendor ships it. There is no fuzzy matching. - Versions are compared as dotted numbers. A version without a digit is not assessed.
- Possible, not confirmed: these findings are shown, de-emphasized, but never alert and never count toward the posture score. KEV and severity come from the CVE.
- Severity: an OSV record without its own CVSS borrows the highest score
of an aliased NVD CVE;
cvss_fromnames the CVE. - Application CVEs only (CPE part
a) are stored. Kernel and OS CVEs are not, so their OSV records keepunknownseverity unless they carry a CVSS. - Rate limits: requests are paced at NVD’s documented limit, one every 6 seconds without a key; an API key gives ten times the rate. The first sync reads about 150 pages of 2,000 CVEs and stores each one, so expect an hour or more, during which the worker matches nothing else. Rate-limited pages are retried twice; a sync that still fails retries 15 minutes after it ended.
- API key: set it through
VULN_NVD_API_KEY; a flag value shows in the process list. It is sent only as theapiKeyheader. - Turning it off removes the NVD data on the next worker tick: possible findings resolve and borrowed severities revert.
Alerts
Section titled “Alerts”A vuln_finding alert rule fires when a node gains an open confirmed finding at
or above the rule’s threshold:
| Threshold | Matches |
|---|---|
kev |
Known-exploited findings only |
critical |
Critical findings, plus all known-exploited |
high |
High and critical, plus all known-exploited |
medium |
Medium, high and critical, plus all known-exploited |
low |
Low and above, plus all known-exploited |
any |
Everything, including unknown severity |
How it fires:
- Where: the watcher runs in osctrl-tls, so it needs
--alerts-enabledand--vuln-enabledthere. The API refusesvuln_findingrules when vulnerability monitoring is off. - Grouping: findings are grouped per advisory, so one advisory affecting many nodes is one notification.
- Cap: each rule sends at most 10 advisories per environment per hour, worst first within a sweep, plus one digest per hour for the rest. The budget lives in Redis, so it holds across sweeps and osctrl-tls replicas; an inventory rollout or a fleet-wide re-match can’t page once a minute.
- Once only: a finding alerts when it first appears, including on a node’s first inventory. A reopened finding doesn’t alert again.
- Not re-alerted: a finding that later becomes known-exploited (added to
CISA KEV) or rises in severity does not alert again. A
kevrule fires only for findings that are already KEV when recorded. - Restarts: a Redis cursor (
osctrl:alert:vuln:cursor) prevents re-alerting. Deleting the cursor restarts from the newest finding; it never replays history. - Possible findings: never alert.
Posture score
Section titled “Posture score”With --posture-enabled and --vuln-enabled on osctrl-api, nodes get a
“Known vulnerabilities” control (ISO 27001 A.8.8):
| Open confirmed findings | Result |
|---|---|
| Any KEV or critical | Fail at critical weight; risk level critical |
| Otherwise any high | Fail at high weight |
| Otherwise any medium, low or unknown | Warn |
| None | Pass |
The control is unevaluated, never passed, before advisory data has loaded and for a node without findings that has never been matched or has no assessed packages (Amazon Linux, an OS-only Windows node, an ecosystem whose feed hasn’t synced). A node with no findings but some packages not assessed warns: not assessed is never clean. Packages only NVD names cover (Windows programs, macOS apps, Homebrew, Chocolatey) never count either way, and possible findings never affect the score.
Health
Section titled “Health”The health page shows an “Advisory feeds” component:
- degraded when a feed’s last error is newer than its last success;
- unknown before the first OSV sync;
- stale after three missed sync intervals.
Air-gapped deployments
Section titled “Air-gapped deployments”Mirror the OSV bucket layout (<ecosystem>/all.zip,
<ecosystem>/modified_id.csv, <ecosystem>/<id>.json) and the KEV JSON on
an internal web server. Then set --vuln-osv-url and --vuln-kev-url. Only
http and https URLs are accepted. Leave NVD off, or point --vuln-nvd-url
at a mirror that serves the API’s responses.
Registered on osctrl-api only when --vuln-enabled is set:
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/vulnerabilities/profiles |
Query profiles to apply to an environment’s schedule |
GET |
/api/v1/vulnerabilities/feeds |
Advisory feed status |
POST |
/api/v1/vulnerabilities/feeds/sync |
Request a feed sync |
GET |
/api/v1/vulnerabilities/{env}/findings |
Findings for an environment |
GET |
/api/v1/vulnerabilities/{env}/summary |
Severity and KEV summary |
GET |
/api/v1/vulnerabilities/{env}/advisories/{id} |
One advisory |
GET |
/api/v1/nodes/{env}/node/{uuid}/vulnerabilities |
Findings for one node |
Operations
Section titled “Operations”- Downloads are capped by
--vuln-max-download-mb. Archives are read in memory and never extracted. - Failed syncs keep existing data and retry after 15 minutes. The API
reports feeds as
staleafter three missed intervals andloaded=falsebefore the first OSV sync. - Resolved findings are kept for
--vuln-retention-days. - Deleted nodes: rows of deleted nodes are swept hourly.
- Log sinks: inventory results also flow to the configured log sinks, like any scheduled query.
Audit log retention
Section titled “Audit log retention”Separate from findings: the API prunes audit rows older than the global audit_log_retention_days setting (default 90, range 1–36500), at startup and hourly. Edit it through the settings API.