GitHub
10/10/2026, 4:57 PMvuln_escalations table whenever an open, confirmed finding becomes known-exploited or rises in severity. It stores the previous severity and KEV state.
• Escalations are recorded in the same transaction that copies advisory flags to findings, and by the matcher.
• Possible and resolved findings never escalate.
• Rows are pruned after 30 days.
• Alerting: the osctrl-tls watcher reads escalations through a second Redis cursor (osctrl:alert:vuln:escalation-cursor).
• An escalation alerts only the rules it newly matches. A kev rule fires when CISA lists the CVE. A high rule fires when a medium finding becomes high, but not when a high finding becomes critical.
• Details read "now known exploited" or "was medium". The entity gets a :escalated suffix, so the cooldown keeps it apart from the original alert.
• Escalations have their own hourly budget and digest, separate from new findings. An escalation flood, as when NVD is first turned on, can't crowd out a new finding.
Watcher hardening (osctrl-tls)
• One sweeper at a time: a Redis lock lets only one replica sweep per interval.
• Settling without clocks: a sweep takes only IDs the previous sweep already listed. A finding committed late by a slower transaction is therefore never passed by the cursor. Alerts arrive about a minute later.
• Cursor lifetime: cursors are saved every sweep with a 7-day expiry. Re-enabling alerts after a long pause starts from now instead of replaying history.
• Corrupt cursors reset to the newest record instead of stopping alerts.
• Faster drain: the batch size goes from 2,000 to 20,000, so a large backlog drains about ten times faster.
Alert rules
• Pattern fields: vulnerability and node-state rules ignore pattern fields when compiling. A stray match_type no longer keeps a stored rule from loading.
• Editing with the feature off: existing vuln_finding rules stay editable, so they can be disabled while --vuln-enabled is off. Only turning another rule into a vulnerability rule needs the feature.
Posture
• Category collision: posture records in categories the evidence source owns (vulnerabilities) are dropped. A posture query can no longer grade, or pass, the "Known vulnerabilities" control.
• Which nodes are scored: a node without findings is evaluated when its last match assessed at least one package and its inventory is under 30 days old.
• Clean nodes no longer lose the control after every advisory sync.
• Nodes silent long enough that they're no longer re-matched stop passing.
Feeds and storage (osctrl-api)
• Migration: adding vuln_node_state.assessed re-matches every node once, so clean nodes keep their control right after upgrading.
• Rejected records: a batch is stored in one transaction. Only if that fails is it retried one record per transaction.
• A record the database rejects is skipped and counted.
• If every record fails, the sync fails and keeps its cursor.
• There are no per-record savepoints, which keeps Postgres from piling up subtransactions.
• NVD key length: vendor:product keys longer than the 255-character column are dropped.
• Resume: a failed full NVD sync resumes from its next page with its original start time (vuln_sync_state.resume_index / resume_start).
• Mixed NVD settings: a replica with --vuln-nvd-enabled stamps vuln_worker_state.nvd_active_at and nvd_active_by (its host).
• A replica with NVD off keeps NVD data, and logs a warning, while another host used NVD in the last hour.
• On a single host, turning NVD off still drops the data on the next tick.
Metrics
With --metrics-enabled and --vuln-enabled, osctrl-tls's existing metrics server exports these gauges:
• osctrl_vuln_advisories
• osctrl_vuln_feed_last_success_timestamp_seconds{source}
• osctrl_vuln_findings_open{confidence}
• osctrl_vuln_findings_resolved
• osctrl_vuln_nodes_pending_match
• osctrl_vuln_packages_not_assessed
They're read from the shared database and cached for a minute. A failed read exports nothing rather than zeros.
API and frontend
• Summary: the summary gains kev_possible, and the KEV card notes how many possible matches it left out. OpenAPI is regenerated.
• Flaky test: NodeConsolePage.test.tsx mocked useNavigate with a new function on every render, so the console reopened its session on each re-render.
• The mock is now stable, and a test asserts one session per console.
• The osquery-prompt test had only passed because those re-renders wiped the scrollback. It now checks the command bar's prompt.
Docs
pkg/vulns/README.md (escalations, cursors, lock, settling, metrics, replica warning, resume), ARCHITECTURE.md (vuln_escalations) and the spec's implementation notes.
Security and operational impact
• With --vuln-enabled off, behaviour is unchanged, except for two alert-rule fixes that apply to all rules:
• pattern fields are ignored for non-text sources;
• existing vulnerability rules stay editable.
• Schema (AutoMigrate, additive):
• a new vuln_escalations table;
• vuln_node_state.assessed triggers a one-time re-match;
• vuln_worker_state.nvd_active_at and `nvd_active_by`;
• vuln_sync_state.resume_index and resume_start.
• New Redis keys:
• `osctrlalertvuln:escalation-cursor`;
• osctrl:alert:vuln:sweep (the lock);
• budget keys now include a kind segment (new or esc).
• Alert volume: turning NVD on can escalate many findings from unknown severity. The separate hourly budget bounds this to 10 advisories plus one summary per rule and environment per hour.
• Metrics: every TLS replica exports the same fleet-wide numbers, so aggregate with max(), not sum().
• Known limitations:
• A finding whose severity drops and later rises again alerts again.
• An escalation recorded while its finding was resolved can alert if the finding reopens.
• The sweep lock isn't extended for sweeps longer than 50 s.
• The metrics queries have no timeout.
• A finding that turns KEV before its first sweep can alert twice for a kev rule.
Validation
• gofmt -l pkg cmd (empty), go vet ./..., go build ./...
• `go test ./... -count=1`: all 55 packages pass
• `golangci-lint run ./pkg/... ./cmd/...`: 0 issues
• `make openapi-check`: the diff adds kev_possible to vulns.Summary
• No go.mod or go.sum changes
• `cd frontend && npm run check && npm test`: 492 tests pass in 3 consecutive full runs
• Live NVD check: the real API accepted the request format. A recent page and CVE-2023-4863 parsed correctly (CVSS 8.8, high, nine products with grouped ranges).
• Mutation checks, each confirmed to fail the tests:
• the escalation "newly matches" filter;
• …
jmpsec/osctrlGitHub
10/10/2026, 5:55 PM