Skip to main content

CTEM Heat Map

The Heat Map is your continuous-exposure dashboard. It aggregates every finding from every scan in your tenant — manual, scheduled, adversary-driven — into one grid: assets down, controls across. One glance shows which assets are getting worse.

Where to find it: https://app.cybrium.ai/findings/heat-map (also in the sidebar under Heat Map between ASPM and Compliance).

What you see

Top bar

  • Window picker — 7d / 30d / 90d. Resets the aggregation window.
  • 4 KPIs — total findings, critical open, high open, regressed in last 7 days. Regression count is the headline number to wake up to.

Grid

Rows are assets (whatever Finding.host was set to — domain, IP, hostname, repo URL). Columns are control families (resolved from ScanJob.scan_type — Email Security, SSL/TLS, DAST, M365, Secret Scan, etc.).

Each cell shows:

  • Number — total findings in that (asset × control) over the window
  • Background colour — opacity scaled by count, hue keyed to the cell's max severity (red = critical, orange = high, amber = medium, blue = low, slate = info)
  • +N chip (when present) — rules that regressed in the last 7 days
  • Sparkline — 7-bucket trend showing when findings landed within the window

Click any cell to open a side panel with:

  • Per-cell tile of counts (findings, regressed-7d, scan count)
  • Sparkline
  • "Open underlying findings" button (filters /findings by host + scan_type)
  • "Edit schedule for this control" button (jumps to Settings → Scheduled Scans)

Recent regressions

Below the grid, a table of the 10 most recent regressions across the tenant. A "regression" means a rule that first appeared in the last 7 days against an asset Cybrium was already scanning before that window — distinguished from "we just started monitoring this asset". Each row links straight into the relevant finding.

How the numbers are computed

count = COUNT(Finding) where scan.completed_at >= now - window
severity_max = max(severity) per (host, scan_type) cell — critical > high > medium > low > info
open = same as count (status taxonomy is on the roadmap; today all findings in window are treated as open)
regressed_7d = number of distinct rule_ids in the cell whose earliest first-seen is in the last 7 days
AND the cell's earliest scan completion across history is BEFORE the 7-day window
trend_30d = 7-bucket histogram of new findings, equally spaced across the window

Source of truth: the Finding table joined to ScanJob for completed_at + scan_type. No new model writes — every read is recomputed.

Demo path

  1. Settings → Scheduled Scans → create 3-4 schedules (Email Security daily, SSL weekly, M365 daily, Secret Scan weekly)
  2. Wait one day for them to fire (or click Run now on each to seed)
  3. Open Heat Map → grid populates with cells per (target × scan_type)
  4. The 7-day window shows what's new vs. just-discovered
  5. Click any cell with +N to see the regression detail

API

curl -fsSL https://app.cybrium.ai/api/scans/findings/heat-map/?window_days=30 \
-b "$AUTH_COOKIE"

Returns a JSON payload shaped like:

{
"generated_at": "2026-06-02T...",
"window_days": 30,
"axes": {
"assets": [ { "key": "example.com", "label": "example.com", "kind": "domain", "criticality": "high" } ],
"controls": [ { "key": "email_security", "label": "Email Security ..." } ]
},
"cells": [
{ "asset_key": "example.com", "control_key": "email_security",
"severity_max": "high", "count": 3, "open": 3, "regressed_7d": 1,
"trend_30d": [0,0,1,1,2,2,3], "scan_count": 4 }
],
"regressions_recent": [
{ "asset_key": "example.com", "control_key": "email_security",
"rule_id": "cymail.dnsbl.listed", "first_seen": "...", "severity": "high" }
],
"summary": {
"total_findings": 27, "open": 27, "critical_open": 2, "high_open": 9,
"regressed_7d": 4, "asset_count": 6, "control_count": 5, "scan_count": 18
}
}

Limits

  • Computed on read; large tenants (>10k findings in window) will see slower loads. A snapshot table is on the roadmap.
  • "Open" is a count proxy today — the Finding status taxonomy (resolved / false_positive / accepted-risk) lands in a follow-up sprint; today the heat map treats every finding in the window as open.
  • Asset criticality is inferred from inventory.Asset.business_unit + a hostname heuristic ("prod", "payments", "api.", etc.). For accuracy, set business_unit explicitly on your Asset rows.
  • No cross-tenant view — this is a per-tenant page. Platform-admin cross-tenant heat map is a future sprint.