Security Program

Vulnerability Recurrence Rate: When Fixes Do Not Stick

22 September 2026 9 min read

A finding closes on Tuesday and reappears on the next scan cycle wearing the same CVE ID, the same port, and the same asset. Most vulnerability programs record this as “one more ticket.” It is not. A finding that keeps coming back is telling you something about the system that produced it, not about the engineer who patched it last time. If your recurrence rate is climbing and nobody owns that number, you are patching the same hole every month and calling it progress.

Recurrence, reopening, and never-actually-fixed are three different failures

Teams collapse these into a single bucket labeled “still there,” which hides three distinct root causes and three distinct fixes.

  • Reopened: the finding was closed, the closure was wrong, and a rescan within the same verification window shows the original condition never changed. This is a validation failure, usually a scanner false negative on the confirming scan or a remediation that only touched a subset of affected instances.
  • Never-actually-fixed: the ticket was closed on a status update, not on evidence. A patch was scheduled, a change ticket was approved, someone marked the vulnerability record “resolved” because the maintenance window happened, but no rescan ever confirmed the package version or configuration actually changed on the host.
  • Recurring: the finding was closed correctly, verified correctly, and came back weeks or months later because whatever produced it the first time produced it again. This is the one that matters for this article, because it means the fix did not survive contact with your deployment process.

The distinction matters because the response is different. Reopened findings need better verification gates before closure. Never-actually-fixed findings need to make closure conditional on rescan evidence, not on ticket status. Recurring findings need someone to look upstream of the vulnerability at the process that keeps regenerating it. Mixing all three into one “recurrence rate” metric makes the number meaningless and the remediation plan wrong for at least two of the three cases.

How to tell it is actually the same vulnerability

Before you can measure recurrence you need a stable identity for a finding that survives rescans, host replacement, and image rebuilds. IP address is not that identity: DHCP leases rotate, autoscaling groups recycle instances, and a host that gets torn down and rebuilt gets a new address but is, functionally, the same recurring problem if the underlying image is unpatched.

A workable identity key combines four fields: the scanner plugin or CVE identifier, a stable asset fingerprint (instance ID, machine UUID, or container image digest rather than IP or hostname), the affected port or component path, and a hash of the evidence string the scanner returned. Two findings match as “the same vulnerability recurring” when the first three fields match across two scans separated by at least one full patch cycle, even if the hostname or IP changed in between. This is why asset inventory needs to track identity independent of network address: without a durable asset fingerprint, a rebuilt server looks like a brand-new host with a brand-new finding, and your recurrence count silently resets to zero every time infrastructure churns. Consolidating results from multiple scanners makes this harder, not easier, since each tool tends to name the same host differently; platforms such as SITEY that normalize findings from all 17 integrated scanners against one asset record before deduplication are solving this specific problem rather than leaving it to a spreadsheet join.

For containers, tie the fingerprint to the image digest, not the tag. A tag like app:latest can point to five different digests over a month. If the same CVE shows up in three different digests built from the same Dockerfile, that is not three unrelated findings, it is one recurring finding with a base image problem. Pull the digest history and diff the base image layer against the last known-good build before opening a new ticket.

Root cause families behind recurring findings

In practice, recurring findings cluster into a small number of patterns. Knowing which pattern you are looking at tells you who should own the fix.

Root cause Typical signal What actually needs to change
Golden image drift Same CVE reappears on every new instance launched from a base AMI or container base image Rebuild and re-bake the golden image, not the running instance
Infrastructure-as-code drift A Terraform or CloudFormation apply reverts a hardened setting because the module still references the old value Update the IaC source, not the live resource, and add a drift check to the pipeline
Rollback or DR restore Vulnerability reappears exactly at a known restore or failover date Patch the backup or snapshot source, not just production
Dependency pinning A lockfile or requirements pin reintroduces the vulnerable version on every clean build Update the pin in the manifest and commit it, not the installed package on one server
Manual point fix Fix applied by SSH or RDP with no corresponding commit, playbook change, or config management update Move the fix into configuration management so the next convergence run does not undo it

The common thread across all five is that the fix was applied to the running artifact instead of the artifact’s source of truth. Patching a live instance feels like progress and clears the ticket, but if the next deploy, rebuild, restore, or convergence cycle pulls from an unpatched source, the vulnerability is scheduled to return whether anyone notices or not. This is the operational argument for treating patch management as a pipeline concern rather than a per-ticket one: the patch needs to land in the same place the next build will read from.

Golden images are the most common repeat offender

If your infrastructure launches from pre-baked images, check first whether the same finding shows up across multiple instances launched in different weeks from the same image ID. If it does, every individual remediation ticket against a running instance is wasted effort until the image itself is rebuilt and the fleet is rotated onto it. Track image age against last-patched date as a leading indicator, not just the vulnerability count.

IaC drift hides in modules nobody re-reviews

A security group rule, a TLS minimum version, or a default password gets hardened manually in the console, the finding closes, and the next terraform apply silently reverts it because the module’s default was never updated. Grep your IaC repository for the specific parameter tied to the finding before closing it. If the parameter exists in source and does not match the remediated value, you have found the actual root cause and the ticket has been closing against a downstream symptom for months.

Set a recurrence threshold that opens a process ticket, not another remediation ticket

Pick a concrete number and stick to it. A defensible starting point: if the same finding identity (per the fingerprint above) reappears three times within a rolling 90-day window, or twice in a row on the same asset lineage, stop routing it as a standard remediation ticket. Route it instead to a problem record against the build or deployment process that produced it.

Write the threshold into your ticketing rules, not into a wiki page nobody checks:

  1. Count = 1 or 2 within 90 days: standard remediation SLA applies, assign and patch as usual.
  2. Count = 3 within 90 days, same asset lineage or image family: auto-escalate to a problem ticket tagged with the root cause category, and freeze further point tickets against that lineage until the problem ticket has an owner.
  3. Count >= 4, or recurrence across more than one asset lineage sharing a base image: escalate to the team that owns the pipeline or image, with the CISO or vulnerability management lead copied, and require a written root cause before the next release train.

The reason to hardcode the threshold rather than leave it to judgment is that judgment is exactly what let the finding recur three times already. A rule that fires automatically on the third occurrence removes the awkward decision of whether this specific recurrence is “bad enough” to escalate, which is a decision people reliably defer.

Assign recurrence to the system owner, not the last person who closed the ticket

Individual assignment is the wrong unit for a recurring finding. The engineer who closed the ticket last time did their job: they patched the instance they had access to. The instance was rebuilt six weeks later from an image they do not own, and the vulnerability came back. Assigning the reopened ticket to that same person again produces the same outcome a third time, because the actual fault is upstream of anything they control.

Recurrence should route to whoever owns the artifact the finding traces back to: the image maintainer for golden image drift, the module owner for IaC drift, the backup or DR lead for restore-driven recurrence, the package or platform team for dependency pinning issues. This requires your vulnerability lifecycle tracking to record not just who closed the finding but which build, image digest, or IaC commit was in place at closure time, so the next recurrence can be traced to whether that specific artifact changed or not. Without that link, ownership defaults to whoever is holding the ticket when the alert fires, which is almost never the right person.

Group ownership by service or asset class rather than by individual name for this reason. A group tied to “payment-api base image” survives staff turnover in a way that a name in an assignee field does not, and it gives the recurrence count somewhere stable to accumulate against.

Use recurrence data to make the case for pipeline work over one-off patching

Point remediation is cheap to schedule and expensive in aggregate. Ten instances of the same finding on ten hosts built from the same drifted image cost ten remediation windows, ten change approvals, and ten rescans, and the eleventh host built from that image will fail again next week. Rebuilding the golden image costs one engineering effort and removes the finding at the source for every future instance.

The recurrence report is the artifact that wins this argument with a budget owner who is not going to take “trust me” for an answer. Pull three numbers before the conversation: the recurrence count for the finding family over the last two quarters, the aggregate remediation hours spent on point fixes against that family, and the number of distinct assets that trace back to the same image or IaC source. When ten point fixes have consumed more engineering time over two quarters than a single image rebuild would take, the pipeline investment is not a nice-to-have, it is the cheaper option on the spreadsheet you already have.

This is also where retest discipline pays for itself. Platforms that automate this phase, such as SITEY, re-test the specific finding against the same asset fingerprint instead of trusting a patch command’s exit code, which is what makes it possible to say with confidence that a finding actually closed rather than simply that a script reported success. That distinction is what separates a real recurrence count from a number inflated by false closures, and it is why retest and closure needs to be evidence-based rather than status-based if the recurrence metric upstream of it is going to mean anything.

Track recurrence rate as its own KPI, separate from open finding count and separate from mean time to remediate. A program can have excellent MTTR and a terrible recurrence rate at the same time, and that combination is the specific signature of a team that is very good at patching symptoms and has never fixed a root cause. Report it by image family and by IaC module, not just as a single fleet-wide percentage, because the fleet-wide number will hide the one drifted base image responsible for most of the repeat volume.

About SITEY

SITEY is an autonomous vulnerability management platform. It discovers, validates, prioritizes, remediates and re-tests vulnerabilities through an eight-phase automated pipeline, unifying output from 17 integrated scanners. SITEY is self-hosted: it runs in your own infrastructure and your findings are stored there. Outbound connections are limited to licence activation and the optional services you enable, such as an AI provider, CVE enrichment and patch catalogues. Pricing is 599 USD per month or 5,999 USD for a perpetual lifetime license. See pricing or how the platform works.

SITEY closes the loop, not just the report.Discover, validate, fix and verify in your own infrastructure.

See pricing