Metric · Security

What is open critical vulnerabilities, and how do you measure it in Metabase?

Open critical vulnerabilities is the count of open critical and high-severity findings across your environment, broken down by asset group and by how long each has been open. It's the headline number for exposure — the thing the CISO gets asked about after every headline CVE. Measure it in Metabase from findings synced from Tenable, Wiz, Snyk, or Orca Security.

TL;DRCOUNT(findings WHERE severity IN ('critical','high') AND status = 'open'), deduped to one row per asset-vulnerability pair, split by asset group and age bucket. The age split is the part leadership actually needs.

What open critical vulnerabilities measures

It measures how much known, serious risk is sitting unfixed right now. The raw total is a blunt instrument — what makes it actionable is the two splits: by asset group, so each owning team sees its own backlog, and by age, so a pile of ninety-day-old criticals can't hide behind this week's fresh scan results. Read it alongside mean time to remediate — one is the stock, the other the flow.

What data does it need?

  • A deduped vulnerability_findings table: asset_id, vulnerability ID, severity, status, first_seen_at — one row per logical finding, not per scan result.
  • An assets table with asset_group (and owner) so counts land with the right team.
  • A normalized severity scale mapped from each scanner's native scoring at sync time.
  • Daily snapshots of open findings if you want a trend — the live table only knows the present.

SQL patterns

Open criticals by asset group and age bucketPostgreSQL
SELECT
  a.asset_group,
  CASE
    WHEN CURRENT_DATE - f.first_seen_at::date < 30 THEN '0-29 days'
    WHEN CURRENT_DATE - f.first_seen_at::date < 90 THEN '30-89 days'
    ELSE '90+ days'
  END AS age_bucket,
  COUNT(*) AS open_findings
FROM vulnerability_findings f
JOIN assets a ON a.id = f.asset_id
WHERE f.severity IN ('critical', 'high')
  AND f.status = 'open'
GROUP BY 1, 2
ORDER BY 1, 2;
Weekly trend of open criticals (from daily snapshots)PostgreSQL
-- Requires a daily snapshot table: one row per open finding per day.
-- The live findings table only knows the present, so back-build history
-- by snapshotting open findings on a schedule.
SELECT
  date_trunc('week', snapshot_date) AS week,
  ROUND(AVG(open_criticals), 0) AS avg_open_criticals
FROM (
  SELECT
    snapshot_date,
    COUNT(*) AS open_criticals
  FROM vulnerability_findings_daily
  WHERE severity IN ('critical', 'high')
    AND status = 'open'
  GROUP BY snapshot_date
) daily
GROUP BY 1
ORDER BY 1;

Pitfalls

Counting per-scan rows instead of deduped findings.→ A finding that persists across ten scans is one problem, not ten. Dedupe to one row per asset-vulnerability pair before counting, or every number on the dashboard is inflated by scan frequency.
Mixing CVSS and vendor severity scales.→ Tenable's VPR, Wiz's attack-path ranking, and raw CVSS don't agree on what "critical" means. Normalize to one severity column at sync time and filter on that — never on raw vendor fields across sources.
No snapshot history, so no trend.→ The findings table only describes today. Without a daily snapshot job, "are we getting better?" is unanswerable — start snapshotting before anyone asks.
Treating accepted-risk findings as open.→ A finding with a documented risk acceptance isn't unaddressed work — it's a decision. Track accepted findings in their own status so the open count reflects the real backlog, and review acceptances on a schedule.

Where this metric applies

Metrics

Dashboards

FAQ

What counts as a critical or high finding?
Whatever your normalized scale says — and that's the catch. Scanners disagree: Tenable uses VPR alongside CVSS, Wiz ranks by attack-path context, and CVSS itself changed between v2 and v3. Map every vendor score to one shared severity column at sync time, document the mapping, and filter on that column — never on raw vendor fields mixed across sources.
Why is my count higher than the scanner console shows?
Almost always duplicate rows. A scanner emits a result row per scan, so a finding that persists across ten scans shows up ten times unless you dedupe to one logical finding per (asset_id, vulnerability_id) pair. Consoles dedupe for you; a raw export doesn't. Model the deduped findings table first, keep per-scan rows in a separate raw table.
How do I trend open criticals over time?
You need snapshots. The live findings table only describes today — once a finding closes, yesterday's count is unrecoverable. Schedule a daily job that copies open findings into a vulnerability_findings_daily snapshot table, then trend weekly averages from it. Pair the trend with mean time to remediate to tell whether a falling count means fixing or just slower scanning.
How do you calculate open critical vulnerabilities?
Count deduped findings where severity IN ('critical', 'high') and status = 'open', grouped by asset group and age bucket. The age split matters as much as the total: fifty criticals opened this week is a busy patch cycle, fifty open for ninety-plus days is a process failure that SLA compliance will confirm.
How do you track open critical vulnerabilities in Metabase?
Sync findings from Tenable, Wiz, Snyk, or Orca Security into a deduped vulnerability_findings table with severity, status, and first_seen_at. Chart the count by asset group and age bucket, snapshot daily for the trend, and put both on a vulnerability management dashboard as part of your security analytics stack.