BSides Charm · 2026

Why Vulnerability
MTTR Alone
Misleads
Use MTTR and MOVA to Measure Remediation Progress
Caleb Kinney

MTTR just doubled

Dashboard metric

Last month

15d

This month

31d

Dashboards call this failure.

Better question: what actually changed?

What happened to backlog age? What happened to open risk?

This actually happened

MTTR went up after we made it a KPI.

  • We made MTTR visible
  • Teams started closing older backlog, not just recent work
  • The metric got worse

The review question was:

“Why are we getting worse?”

That was a fun meeting.

MTTR rose because we finally closed older backlog, not just recent work.

Two Metrics, Two Questions

Flow

MTTR — Mean Time to Remediate

How fast are we closing work?

Backlog Age

MOVA — Mean Open Vulnerability Age

How old is the open backlog today?

Flow vs Backlog Age

Same records, different slices

Resolved records

MTTR

Closed findings only.

Open records

MOVA

Open findings only.

Same export. Different slice. Different signal.

MTTR measures closed work

mttr = (
    vulns.filter(pl.col("resolved_at").is_not_null())
    .with_columns(
        (pl.col("resolved_at") - pl.col("created_at")).dt.total_days().alias("age_days")
    )
    .select(pl.col("age_days").mean().alias("mttr_days"))
)

The flow signal comes from resolved records.

MOVA measures backlog age

mova = (
    vulns.filter(pl.col("resolved_at").is_null())
    .with_columns(
        (pl.datetime_now() - pl.col("created_at")).dt.total_days().alias("age_days")
    )
    .select(pl.col("age_days").mean().alias("mova_days"))
)

The backlog-age signal comes from open records.

You already have the data

You do not need a new platform.

  • MTTR and MOVA use the same records
  • A CSV export is often enough
  • MOVA comes from open findings
  • Older backlog often shows risk that averages hide

SLA views tell you if you hit a deadline, not the backlog age you still carry.

A simple simulation

Hold constant

Setup

Start with one backlog.

Keep arrivals and capacity fixed.

Change one thing

Closure pattern

Newest-First and Oldest-First are simplified closure patterns.

They are used only to reveal metric behavior.

Same team. Same capacity. Different simulated closure pattern.

This is not a remediation policy. It is a metric demonstration.

Prioritization is risk-based.

Real prioritization is risk-based

  • Severity

  • Exploitability

  • Exposure

  • Asset criticality

  • Threat intelligence

Not newest-first. Not oldest-first.

Metrics do not drive the queue. They show the consequences.

MTTR reflects recent closures

Line chart of MTTR over time under two simplified closure patterns. MTTR looks lower when recent findings close first because it reflects age at closure.

Newest-First looks better on MTTR alone.

MTTR is windowed. Label the window before calling a regression.

MOVA shows backlog age

Line chart of MOVA over time under two simplified closure patterns. It shows the age of work still open today under each pattern.

MOVA reveals the backlog you are carrying.

MOVA is not windowed. It reflects the backlog right now.

Paradox

The MTTR Paradox

MTTR

while

MOVA

MTTR can rise while backlog age falls.

When the dashboard shows only MTTR

Oldest-First

92.6

MTTR

Newest-First

16.1

MTTR

A one-metric dashboard makes that the verdict.

What MOVA shows instead

Oldest-First

36.5

MOVA

Newest-First

726.4

MOVA

On backlog age, the story changes.

Same data, different signals

Same data, different signals
Each metric answers one side of the earlier split.
Flow Backlog Age Interpretation
MTTR MOVA Interpretation
Oldest-First 92.6 36.5 MOVA is lower
Newest-First 16.1 726.4 MTTR is lower

If you read only MTTR, you may misread progress.

Which signal are you reading?

Both are partial signals

Each signal is intentionally partial.

The mistake is reading either one alone.

When the dashboard shows both core signals

Newest-First

MTTR 16.1 days

MOVA 726.4 days

Oldest-First

MTTR 92.6 days

MOVA 36.5 days

The disagreement is now visible.

Interpret it before judging progress.

Read the signals like a system

MTTR + MOVA

System improving

MTTR + MOVA

Clearing older backlog

MTTR + MOVA

Backlog is aging

Read them together or you may misread progress.

What to report

Monthly view

MTTR + MOVA

Put both on the same monthly view.

Definitions

Flow + backlog age

Use the earlier split consistently.

Review rule

Read together

Review MTTR with MOVA visible.

Framing

Label the window

Label the MTTR time window before calling a regression.

Add data science to your toolbox

Once the metric is defined, make the analysis reproducible.

Define the metric in code before it reaches the dashboard.

Define the metric
Define the logic in code before it hits the dashboard
Polars
Inspect the raw data
Inspect raw records early to catch edge cases
Positron
Visualize the trend
Visualize flow and backlog age over time
Plotnine
Summarize the backlog
Summarize the current state at a glance
Great Tables
Explore the metric
Turn the analysis into a simple app people can inspect
Shiny for Python
Publish the analysis
Publish code, charts, and narrative together
Quarto

Start with an export. Define the metric in code first.

A simple policy

Policy

Track MTTR + MOVA

Review both together in the same view.

Guardrail

Do not drive the queue

Prioritization stays risk-based.

Metrics verify decisions. They do not decide them.

Read the system, not just the metric

Read both signals.

Question the disagreement.

If they disagree, the system is telling you something.

Read both or you may misread progress.