← AppSec Signal

How to Measure Patch Velocity (And Why Most Teams Track the Wrong Metric)

Derek Voss 8 min read
Measuring patch velocity metrics for AppSec teams

Patch velocity is one of those metrics that shows up in security program reviews and board-level risk reports. Teams track it as "mean time to remediate" (MTTR) — the average time from CVE disclosure to patch landing. It looks clean on a dashboard. It's usually measuring the wrong thing.

The problem: MTTR calculated across all CVEs in your queue tells you how fast you're closing tickets, not how fast you're eliminating real risk. If your team is patching every CVSS 7+ advisory within 14 days — including the hundreds that affect code paths your production service never executes — your MTTR looks good while your actual security posture improvement per unit of engineering effort is low.

The metric you actually want is reachability-scoped MTTR: time from CVE disclosure to patch landing, measured only for CVEs with confirmed reachable call paths in your production code.

Why Standard MTTR Misleads

Consider a Node.js service with 280 CVE advisories in the current queue. Of those, 12 have confirmed reachable call paths from production entry points. The remaining 268 are in unreachable packages — transitive dependencies that ship in node_modules but that no live request ever traverses.

A team that patches aggressively might close 40 advisories per sprint. Their MTTR looks excellent. But if they're not prioritizing by reachability, statistically they'll close roughly 40 × (12/280) = 1.7 reachable CVEs per sprint while patching ~38 unreachable ones. They're spending 95% of their patching effort on noise.

Conversely, a team that patches only 5 CVEs per sprint but selects exclusively from the confirmed-reachable queue is closing 5 real risks per sprint. Their MTTR number is higher, but their actual risk reduction per unit of effort is dramatically better.

MTTR isn't a useless metric — it's useful as a proxy for organizational responsiveness. But it needs to be scoped to the subset of CVEs that actually represent runtime risk. Otherwise it's a throughput metric, not a risk metric.

The Metrics That Actually Matter

Here's the metric set we track for Patchlynx's own codebase and recommend to teams using the tool:

Reachable-MTTR. Mean time from CVE disclosure date (per OSV/NVD) to patch landing in main, scoped to confirmed-reachable findings only. Target: under 14 days for CVSS ≥ 7.0 reachable CVEs; under 30 days for CVSS 4.0–6.9.

Reachable backlog count. Current number of confirmed-reachable CVEs open in your queue, by severity band (critical/high/medium). This is the number your security program should be managing toward zero. If it's growing sprint over sprint, your input rate is exceeding your patch rate — which means either dependency hygiene (new packages getting introduced with vulnerabilities) or insufficient sprint allocation for security work.

Unreachable backlog age. Median age of unreachable CVEs in your queue. This isn't urgent, but you should review quarterly. An unreachable package that's been in your tree for 18 months at CVSS 9+ is worth re-examining — either the reachability determination should be revisited, or you should consider removing the package from your dependency tree.

New reachable CVE rate. How many new confirmed-reachable CVEs are being introduced per sprint, on average. If this is consistently 3–5 per sprint and your team can patch 3–5 per sprint, you're in equilibrium. If the rate is higher than your patch capacity, you have a dependency hygiene problem that needs addressing upstream (PR gates, dependency update policies) rather than just patching faster downstream.

Instrumenting These Metrics

The instrumentation challenge is that standard vulnerability management dashboards (Jira Security, GitHub Advanced Security, etc.) track all CVEs in aggregate. They don't natively support a "reachability-scoped" filter because reachability isn't part of the standard advisory data format.

The way we instrument this in Patchlynx: every confirmed-reachable finding gets a Jira ticket with a label (plx-reachable) and a created timestamp. When the fix lands and the rescan confirms the CVE is closed, the ticket gets resolved and we record the resolution timestamp. Reachable-MTTR is the mean of (resolution_date - cve_disclosure_date) across all tickets with that label, in a rolling 90-day window.

If you're not using Patchlynx's Jira integration yet, you can approximate this manually: maintain a spreadsheet or board column specifically for confirmed-reachable CVEs, and track the dates separately from your general advisory queue. It's more friction, but the data is worth having even imperfectly.

One instrumentation detail that matters: use CVE disclosure date as your start timestamp, not the date you discovered or opened the ticket. Using ticket creation date as the start makes your MTTR look artificially good — it hides the time between when the CVE was publicly known and when your team learned about it. Disclosure date is the number that matters for actual risk exposure duration.

Communicating Reachability-Scoped Metrics to Leadership

Security metrics often need to be explained to non-technical stakeholders — VPs, GRC teams, or customers who ask for vulnerability program data in vendor questionnaires. The reachability framing is initially counterintuitive: "We have 280 CVEs but we're only tracking 12 of them as urgent" can sound like under-investment in security.

The framing that works: compare it to a fire alarm system. A fire alarm that goes off for every piece of dust in the air isn't a better security system — it's one that engineers learn to ignore. Reachability-scoped metrics mean we're tracking the fires, not the dust. The 268 unreachable CVEs are in a monitored queue; they're not being ignored, they're being deprioritized relative to confirmed risks.

For external reporting (SOC 2 audits, vendor questionnaires), the relevant number is still the total advisory count — you need to be able to show you're aware of all advisories affecting your dependency tree. The reachability-scoped MTTR is the internal management metric. Don't conflate the two.

A few specific patterns that indicate a problem:

MTTR increasing despite low backlog count. This usually means your team is patching efficiently but the patches are taking longer to ship — perhaps because they're bundled with feature work, or because your release cadence is slow. The fix is process (separate patch PRs, faster release gates for security patches) rather than triage improvement.

Backlog count increasing despite active patching. New reachable CVEs are being introduced faster than they're being closed. Check: are you adding new direct dependencies frequently? Are your existing dependencies being actively maintained and receiving security updates? Is your call graph structure growing in ways that are making previously-unreachable packages reachable?

MTTR looks good but the same packages recur. You're patching a vulnerability in a package, then the same package shows up with a different CVE two sprints later. This is a dependency hygiene issue: the package is either poorly maintained (chronic vulnerability rate), not being kept current (you patch reactively instead of keeping the version current), or is the wrong package for the use case. Consider whether removal and replacement makes more sense than ongoing reactive patching.

Reachability-scoped MTTR isn't a perfect metric. No metric is. But it's a substantially better proxy for actual risk reduction than raw MTTR across all advisories — and it's the number that tells you whether your security engineering effort is landing where it matters.