DedSec Application Security ·
Picture a quarterly security review where one slide gets all the attention: mean time to remediate, down by half since last quarter. Nobody asks how many findings are still open, or how old they are. If the answer is "more than last quarter, and older", the slide is not wrong. It is the average of the findings that closed, and the team stopped closing the hard ones.
Remediation time is a metric often asked for and easily bent, usually without anyone intending to bend it. Every MTTR number is the output of at least five decisions, usually made silently:
This post covers how each moves the number, with small worked examples, and ends with questions to ask of any MTTR figure. It builds on the post on finding identity, which covers how a finding store decides two results are the same finding and what new, fixed and reopened mean. Everything here inherits those decisions and their errors.
The numbers in the worked examples are invented to show the arithmetic and describe no real team or tool. The only external figures come from CISA documents and the CISA KEV catalog, cited where they appear.
There are at least five defensible start events, and each answers a different question.
None is right in general. The mistake is reporting one under the name of another, or mixing them. Time from assignment and time from detection are not comparable: the difference is the triage queue, which can be the slowest part of the process.
First-detected time is a property of the scanner as much as of the code. Three mechanisms make it drift:
New rules. When a rule pack is upgraded or an analyser enabled, every match in existing code is detected that day. Code vulnerable for years gets a first-detected date of this morning. That is fine for measuring response, and badly wrong if anyone reads it as exposure.
Advisory ingestion. The post on why vulnerability databases disagree covers why two databases can carry the same advisory at different times or with different ranges. An SCA finding's detection date is the latest of three: the vulnerable version arriving, the advisory reaching your scanner's data, and the next scan. Storing the advisory's publication date beside first-detected costs one field and lets you report response time and exposure time.
Scan cadence. With weekly scans, a finding introduced the day after a scan is detected six days late, and a fix merged the day after a scan is observed six days late. With a detection clock, each closure is observed up to one scan interval after the fix, so moving from weekly to daily scans makes MTTR fall with nobody fixing anything faster. If cadence changed during the period, say so beside the number.
When a timeframe is binding, the start event has to be written down. CISA's Binding Operational Directive 19-02, issued in April 2019 for US federal agencies' internet-accessible systems, required critical vulnerabilities to be remediated "within 15 calendar days of initial detection" and high ones within 30 calendar days. Its FAQ asks when the 15- and 30-day clocks start, and answers that the directive "starts tracking vulnerabilities from the time of initial detection, not the time when notification is made". The post on CVSS, EPSS and KEV covers BOD 26-04, which replaced it on 2026-06-10. The point here is narrower: the clock started at detection by CISA's scanner, and the directive said so.
The KEV catalog uses a different start. Each entry's dueDate is set from its dateAdded, the day CISA listed the CVE, not from publication or from when you found it. Under BOD 22-01, which BOD 26-04 superseded and revoked on 2026-06-10, the default was two weeks for CVEs assigned from 2021 onward and six months for older ones, adjustable "in the case of grave risk". The catalog's own offsets did not always match that default: in catalog version 2026.10.21, the 1,617 entries added before 2026-06-10 have due dates most often 21 days after listing (1,025 entries), 14 days for 254, and 181 to 184 days for 257. The 137 entries added since then carry offsets of 3 days (111) or 14 days (26). That is why CVE-2015-5477, an ISC BIND vulnerability from 2015, added on 2026-10-08, has a due date of 2026-10-11 rather than six months later. Had it sat in your backlog since 2016, its KEV clock still started in October 2026, while your own had been running for a decade. A finding can be under several clocks at once; a report should say which it measures.
Stop events, from weakest to strongest:
For exposure, only the last means the risk is gone. For engineering response, merge is often the honest stop. The common failure is a metric that stops at 1 or 3 and is read as if it stopped at 5.
Absence on rescan is the stop most likely to flatter. The finding-identity post lists six reasons a known finding can be missing from a scan, only one of them a fix; the others include a failed analyser, an excluded file, an expired DAST session, a renamed rule and a diff-scoped scan, and the post on silent scanner failure covers how a scan that did not run looks like a clean one. Each closes findings at the moment of the bad scan, producing short, fictitious remediation times. A rule upgrade that renames a hundred rules can close a hundred findings in a day and pull a quarter's mean down sharply.
Two habits contain this. Record the stop reason with the stop time, so inferred, asserted and scope-change closures can be counted apart. And exclude from remediation metrics any closure whose closing scan did not cover the finding's location. If that moves the number a lot, the number was measuring the scanner.
A code fix merged on Monday may reach production that day or next month, depending on the release train, and a patched image is not deployed until every workload on the old one has been replaced. The container image post covers why the remediation is rebuild, rescan, redeploy, and why a rebuild that reuses cache is not necessarily patched. If the metric is meant to describe exposure, the stop needs evidence from where the software runs, not where it is built.
This is the error that turns MTTR into a reward for avoidance. Mean time to remediate is usually computed over findings closed in the period: sum their ages at closure and divide by the count. Findings still open are left out, because they have no closing time. That is a selection effect. The open findings are disproportionately the old, difficult ones a remediation metric exists to see. Statisticians call this right-censoring: for an open finding we know only that its remediation time exceeds its current age. Dropping censored observations biases the estimate downward, and the bias grows as the team gets worse at closing hard findings.
These figures are hypothetical. Two teams each receive the same ten findings on day 0, and we measure on day 90.
Team A closes eight of them, on days 4, 6, 9, 12, 20, 35, 60 and 85. Two are still open.
Team B closes the five easy ones on the same days as Team A, 4, 6, 9, 12 and 20, and does not get to the others. Five are still open.
Mean time to remediate over closed findings:
Team B's MTTR is less than half of Team A's, and Team B is doing worse by any reasonable reading. Now count the share of each cohort still open after N days:
The teams are identical for a month and diverge after it, which is what happened. The share-open view shows it; the mean hides it and inverts it.
The median is better, but only computed over the whole cohort, counting open findings as "longer than 90 days", and taken as the first age by which half the cohort has closed. The fifth of ten closes on day 20 for both teams, so both medians are 20 days. That is true, and shows the median's limit: it says nothing about the tail, where the teams differ. A higher percentile does. Team A's 80th percentile is 85 days, the eighth closure; Team B's is "more than 90 days". A percentile you cannot compute yet is itself information.
Real findings arrive continuously, so on the reporting date some have been open a year and some a week. The standard tool is the Kaplan-Meier estimator from survival analysis, which uses each open finding for as long as it has been observed and no longer.
Another invented example, five findings: three closed after 3, 7 and 12 days; two still open, aged 5 and 15 days. At each closure, multiply the share still open by (at risk minus closed) divided by at risk, where "at risk" means findings that reached that age still open.
The estimated median, the first age at which the share still open falls to half or below, is 12 days. The median over closed findings alone is 7 days, and the mean (3 + 7 + 12) / 3 = 7.3 days. The open findings pulled the honest estimate up, as they should.
You do not need a statistics package for most of the benefit. A table with one row per monthly cohort of newly detected findings and columns for the share still open after 7, 30, 60, 90 and 180 days, blank where the cohort is too young, is readable by anyone and resistant to the avoidance effect above. Split it by severity. Report the open count and the age of the oldest finding beside it: a backlog is a stock, remediation time describes a flow, and a programme needs both.
A fixed finding that comes back can be counted three ways.
Whichever you choose, count regressions separately: how many findings reopened in the period, and how long they had been fixed. A falling MTTR beside a rising regression count usually means fixes are not holding, and a reset policy is rewarding the same fix repeatedly.
Under an identity scheme that breaks on edit, a regression is not reopened at all. It closes as fixed and arrives as new: one fast remediation and one fresh finding, with nothing linking them. The finding-identity post's tests are worth running before trusting any regression count.
Flapping is the degenerate case. A finding that disappears and returns on scans of unchanged code, because an analysis budget ran out, a DAST target was rate-limited or two pipelines with different exclusions share a store, closes every time it disappears. Under a reset policy, one flapping finding can contribute dozens of near-zero remediation times in a quarter. Count state transitions per finding, and exclude from remediation metrics any closure that was followed by a reopen with no change to the code or asset in between.
A finding can leave the open backlog in at least five ways, and only two are remediation:
A metric that counts all five as remediated measures backlog throughput, which is worth measuring under its own name. False-positive closures are fast by nature, since triage is quicker than repair, so mixing them in drags MTTR down most when the scanner is noisiest. Risk acceptance can be faster still. Decommissioning is often the best remediation available, and still not the same event as a fix.
Keep a disposition on every closure, and report each disposition's count and timing separately. The post on suppression at scale covers the decisions behind the non-fix dispositions and how they decay. Give risk acceptances an expiry, and treat an expired acceptance as a reopen. BOD 19-02's FAQ applied the same idea to false positives in CISA's own scanning: false-positive status expired 365 days after CISA designated it, and agencies had to resubmit evidence each year.
Closing by assertion deserves a second person for a measurement reason as well as a security one. If whoever benefits from a closure can close a finding alone, as false positive or accepted, the metric measures incentives as much as engineering.
CVSS scores are revised, EPSS scores change daily, a CVE can enter KEV a decade after publication, and an asset can become internet-facing. If your SLA is set by severity, a finding can open under one deadline and be under another a month later. The question for measurement is which deadline SLA compliance uses. Three common rules each mislead differently.
Whichever rule you choose, record every severity change with its date, old and new value and source, and compute compliance from that history. Downgrades need the same care: a finding downgraded the day it would have breached looks, in a compliance report, much like one that was fixed. Report how many findings changed severity, and in which direction, beside the compliance rate.
Most workflows have a state for waiting on a vendor patch, another team or a change freeze. If the clock pauses there, time disappears easily. A finding that spends 80 of its 100 days in "waiting on vendor" reports as a 20-day remediation. Pausing is sometimes right, since nobody can ship a patch that does not exist, but report elapsed time beside paused time, and require a reason and a review date to enter the state. A paused finding with no review date is a risk acceptance that nobody signed.
The unit of count can change MTTR more than any other decision. Another invented example: One CVE in a shared base image is inherited by 40 container images. The platform team patches the base image on day 2. Thirty-eight images are rebuilt on day 5. Two belong to a team that rebuilds monthly, and are rebuilt on day 30.
All three are correct answers to different questions. Per instance measures the work, last instance measures exposure, and root cause measures the platform team. Counted per instance, the 30-day tail becomes a small fraction of a large, fast population, and the exposure becomes a rounding error.
The same choice appears everywhere: one library in a monorepo, or one finding per service using it; one missing header across a hundred hostnames behind one load balancer; one unsafe helper called from fifty places. Cross-engine duplicates matter too: if two analysers report the same injection and both are kept, the fix counts twice. Count at more than one level, use instances for workload and root cause for exposure and SLA, and name the unit beside any single number.
AppSecD findings are deduplicated across scans and move through new, reopened and fixed. They carry ageing and an SLA, they are assigned to an owner, and closing one can require maker-checker approval, so a finding marked fixed or false positive can need a second person to confirm it. Its SAST, DAST, SCA, secret detection, IaC, container, Kubernetes and API security run against one deduplicated finding model, so a vulnerability found by more than one engine is tracked once. Its security intelligence highlights CISA KEV, and its AI triage labels likely false positives, with the label itself tracked.
Maphra's vulnerability management deduplicates findings across scans and tracks them through new, reopened and fixed, with ownership, ageing and SLA.
That is the whole claim. This post does not describe which start or stop event either product uses, how either handles reopens, pauses, severity changes or censored findings, or what remediation metrics either reports. The measurement methods above are general practice, and the questions below apply to any tool's numbers, ours included.
A remediation number that comes with answers to these is a measurement. Without them, it is the average of whatever closed, and a team can improve it by working on fewer of the hard findings.