FeaturesVisual dashboards

Visual dashboards

Pencheff’s findings register, scan history, and CVE rollups have always been queryable. This page covers the chart surfaces layered on top of that data — five dashboards that render the same numbers as the tables, but in shapes a stakeholder can read in five seconds.

Every dashboard reuses primitives in apps/web/components/dashboard/ and renders charts via Recharts. All data comes from existing API endpoints; no scan-time work is added.

Per-scan dashboard

Lives at /scans/{id}/dashboard. Linked from the assessment page once the scan transitions to status="done".

TileWhat it shows
Severity donutCounts from Scan.summary rendered as a donut with center-label total.
CVSS histogramFindings bucketed across 0–2, 2–4, 4–6, 6–8, 8–10. Bar fill maps to the severity that band corresponds to.
Verification pieverification_status distribution: unverified / true_positive / false_positive / fixed.
Category barTop-N finding categories sorted desc; bar fill = the highest-severity finding in that category.
OWASP coverageStacked horizontal bars by owasp_category × severity.
Top-risk listTen findings ranked by risk_score (CVSS as tiebreak), each one a click-through to /scans/{id}/findings/{fid}. Surfaces the PriorityStrip (risk score, reachability, SSVC, EPSS, KEV) inline.
Endpoint treemapTop-N affected endpoints sized by finding count, coloured by top severity.
Stat tilesTotal active · KEV-in-scan · reachable · median EPSS.

Pre-completion the route shows a banner and links back to the live assessment page.

LLM red-team variant

When target_kind === "llm", the same /scans/{id}/dashboard route renders a different composition that reflects what an LLM red-team scan actually does — probing strategies, attack techniques, judge verdicts — rather than CVSS-shaped vulnerability surface.

TileWhat it shows
Verdict funnelThe centerpiece. Total probes flowed in → vulnerable / refused / ambiguous. Built from /scans/{id}/llm-transcripts?format=json. When the transcript file expired or was never written, the dashboard degrades to “vulnerable count = total findings” and surfaces the limitation inline.
OWASP-LLM-Top-10 heatmapOne row per LLM01LLM10 with absolute failure count and per-category success rate (failures ÷ probes-in-category). Built from Scan.summary.llm_redteam_summary.by_category plus the transcript per-category totals.
Strategy breakdownBar chart of by_strategy (base, jailbreak, dataset, custom, guardrail) plus a horizontal-bar list of the top 10 techniques (direct_injection, role_play, constraint_violation, …).
Judge-confidence histogramWhen LLM-as-judge is enabled, distribution of judge.confidence across 10 buckets 0.0–1.0. Cell colour ramps gilt → oxblood for high-confidence vulnerable verdicts.
Token + latency profileStacked bar of total prompt / completion / cached / reasoning tokens (OpenTelemetry GenAI semantic conventions), plus latency p50 and p95 derived from per-probe latency_ms.
Top-10 failures listRendered from Scan.summary.llm_redteam_summary.top_failures — already computed by the runner at plugins/pencheff/pencheff/modules/llm_red_team/reporting.py.
Severity rollup tilesby_severity counts per LLM finding, with critical accented red.
Recommended-guardrails CTALink into /scans/{id}/recommended-guardrails.

The LLM dashboard renders without any new backend work — the redteam_summary payload was already persisted on Scan.summary, just not visualised. Verdict funnel + token profile + latency come from the existing transcript endpoint.

Per-target trend dashboard

Embedded on /targets/{id} once the target has ≥2 completed scans. Powered by GET /dashboard/target/{target_id}/trend.

  • Grade trajectory line — score (0–100) per completed scan over time.
  • Severity stack area chart — severity counts per scan, x-axis = created_at.
  • Stat tiles — open total · merged fixes · MTTR (days, computed from Finding.created_at → last_rechecked_at for findings now verification_status="fixed").
  • Scan-pair delta strip+N new · −N fixed · ±N regressed per consecutive pair, derived from severity-summary diffs (not finding-by-finding identity comparison — accurate enough for trending and avoids an O(scans²) Finding cross-join). Each row links into /scans/compare?a=&b= for a finding-level diff.
  • Empty state — targets with 0 or 1 completed scans don’t show the trend section; one snapshot isn’t a trend.

Per-repo trend dashboard

Lives at /repos/{id}/dashboard. Linked from the repo’s scan-history header when ≥2 scans are on file. Powered by GET /repos/{id}/trend.

  • Severity score over time — weighted score (25 × critical + 10 × high + 4 × medium + 1 × low) per RepoScan, x-axis = completed_at.
  • Severity stack — severity counts per scan, area chart.
  • Stat tiles — open findings · merged fixes · latest commit SHA · severity score for the latest scan.
  • Latest-scan donut + scanner duration list — quick view into the most recent commit’s findings + per-scanner runtime.
  • One-click link into /repos/scans/{id}/dashboard for the latest scan’s full breakdown.

Per-repo-scan dashboard

Lives at /repos/scans/{id}/dashboard. Linked from the scan detail page next to “View compliance mapping”. Reuses /repos/scans/{id} and /repos/scans/{id}/findings (no new backend).

  • Stat tiles — total findings · scanners run · scan duration · fix progress (merged + open / total).
  • Severity donut — distribution across the scan’s findings.
  • Scanner-effort bar — finding count per scanner (semgrep / bandit / gosec / brakeman / eslint / gitleaks / ghsa / yara / trivy_iac / checkov); tooltip shows duration + skipped/error states.
  • File-hotspot treemap — top-N affected files sized by finding count, coloured by top severity.
  • Fix-status pienone / proposed / pr_open / merged distribution. The pivot stat that tells you whether the autofix layer is converting.
  • Top CVEs table — top-12 CVE-tagged findings with installed → fixed version delta and a click-through to the linked PR (when fix_pr_url is set).

Workspace executive dashboard

Lives at /dashboard/executive. Plan-gated on the EXECUTIVE_DASHBOARD feature flag — see the executive-dashboard page for the per-tile breakdown and the API endpoints.

The previous version rendered the 90-day trend as inline SVG; it now uses the same Recharts primitives as the rest of the dashboard suite. The heatmap stays as a CSS grid (Recharts has no good heatmap primitive).

API surface

EndpointPowers
GET /scans/{id} + GET /findings?scan_id=Per-scan dashboard.
GET /scans/{id}/llm-transcripts?format=jsonLLM verdict funnel + token / latency profile.
GET /dashboard/target/{target_id}/trendPer-target trend section.
GET /repos/{id}/scans + GET /repos/{id}/trendPer-repo trend dashboard.
GET /repos/scans/{id} + GET /repos/scans/{id}/findingsPer-repo-scan dashboard.
GET /dashboard/heatmap / /trend / /top-repos / /kev-exposure / /fix-conversionExecutive dashboard.

Code locations

  • Shared primitives — apps/web/components/dashboard/{SeverityDonut,CategoryBar,CvssHistogram,OwaspCoverage,TopRiskList,VerificationPie,EndpointTreemap,TrendLine,SeverityStack}.tsx
  • Repo-specific — apps/web/components/dashboard/repo/{ScannerEffortBar,FileHotspotTreemap,CveTable,FixStatusPie}.tsx
  • LLM-specific — apps/web/components/dashboard/llm/{VerdictFunnel,OwaspLlmHeatmap,StrategyBreakdown,JudgeConfidence,TokenProfile,TopFailuresList}.tsx
  • Severity tokens (single source of truth) — apps/web/lib/sev.ts
  • Backend aggregations — apps/api/pencheff_api/routers/dashboard.py (target_trend), apps/api/pencheff_api/routers/repos.py (repo_trend)