The first time a team turns on a container scanner across all its repositories, the result is usually a dashboard with several thousand findings, a few hundred marked critical. Two weeks later, the dashboard is ignored. The problem is not the scanner; it is the lack of a process that turns findings into a short, ordered list of work with clear owners and deadlines.

This post describes the vulnerability management lifecycle I set up at companies with one or two people responsible for security and a few dozen engineers. It is continuous, it is honest about priorities, and it measures what matters: how long exploitable weaknesses stay open.

What vulnerability management is

Vulnerability management is the continuous process of identifying, evaluating, prioritising, remediating and verifying security weaknesses across systems, applications and networks. The word "continuous" is the important one. New vulnerabilities are published every day, and your systems change every day; a quarterly scan is a snapshot, not a programme.

The lifecycle has six stages:

Discover -> Scan -> Prioritise -> Remediate -> Verify -> Report
    ^                                                    |
    +----------------------------------------------------+

Stage 1: discover your assets

You can only find vulnerabilities in assets you know about. Build an inventory that covers:

  • cloud accounts and the resources in them: instances, containers, functions, databases, buckets;
  • internet-facing hosts, domains and subdomains;
  • source repositories and the dependencies they declare;
  • container images in registries, and what is actually running;
  • laptops and servers, from the MDM and EDR consoles;
  • SaaS applications that hold company data.

For each asset, record an owner (a team, not a person who may leave), its exposure (internet facing, internal, isolated) and the sensitivity of the data it handles. These three attributes drive prioritisation later.

Automate discovery where possible: cloud provider inventories, DNS zone exports and registry listings can be pulled on a schedule, and new assets without an owner raise a ticket.

Stage 2: scan

Different asset types need different scanners:

Asset Scanner type Example tools
Application dependencies Software composition analysis Dependabot, Renovate with advisories, Trivy, OSV-Scanner
Container images Image scanning Trivy, Grype, registry-native scanning
Hosts and VMs Agent or network vulnerability scanning Cloud provider inspectors, OpenVAS
Infrastructure as code Misconfiguration scanning Trivy config, Checkov
Cloud accounts Posture management Provider security hubs and config rules
Web applications Dynamic testing OWASP ZAP in CI, periodic manual tests

Scan on change (in CI for code and images) and on schedule (daily for running assets), because a vulnerability published today affects an image that was clean when it was built.

Stage 3: prioritise

This is where most programmes fail. Sorting by CVSS severity alone produces a list where hundreds of items are "critical" and nothing distinguishes the one that is being exploited right now.

I use four inputs:

  1. Known exploitation: is the CVE in the CISA Known Exploited Vulnerabilities (KEV) catalog?
  2. Likelihood of exploitation: what is its EPSS score, the estimated probability of exploitation in the next 30 days?
  3. Exposure: is the affected asset reachable from the internet, internal only, or isolated?
  4. Severity and impact: the CVSS score, adjusted by the sensitivity of the asset's data.

From these, a simple decision table:

Priority Rule Remediation SLA
P1 In KEV, or EPSS above 0.5, and internet-facing 48 hours
P2 In KEV or EPSS above 0.1 on internal assets, or CVSS 9+ internet-facing 7 days
P3 CVSS 7-8.9, or CVSS 9+ on internal assets with no exploitation signal 30 days
P4 Everything else 90 days or next planned upgrade

The thresholds are a starting point; tune them to your volume. What matters is that the rule is written down, applied automatically, and that P1 really means "drop other work".

Reachability cuts the noise further

Many dependency findings are in code paths the application never calls, or in packages only used at build time. Some tools can analyse reachability or distinguish runtime from development dependencies. Use it to lower priority, not to ignore findings entirely; unreachable code has a way of becoming reachable after a refactor.

A worked example

Three findings arrive on the same morning:

  • A remote code execution CVE in an image library, CVSS 9.8, listed in KEV, used by the public upload service. That is P1: patch or disable the feature within 48 hours.
  • The same CVSS 9.8 CVE in a batch job that only processes files generated internally, with no network exposure. That is P3: fix in the next 30 days.
  • A CVSS 7.5 denial of service issue in a web framework, EPSS 0.02, on an internet-facing API behind a CDN with rate limiting. That is P3 as well.

Without the rule, the two 9.8 findings would look identical and the team would argue. With it, the decision takes a minute and is the same whoever makes it.

Stage 4: remediate

Remediation options, from best to least preferred:

  • Patch or upgrade the affected component.
  • Remove it if it is not needed. Unused packages, base image tools and old services are free wins.
  • Mitigate with a configuration change, a WAF rule or a network restriction while a fix is pending.
  • Accept the risk, in writing, with an owner and an expiry date.

Make remediation cheap so it happens:

  • Automated dependency update pull requests, grouped and with tests, merged regularly rather than in a yearly big-bang upgrade.
  • Base images rebuilt weekly from updated upstream images, so OS-level fixes flow in without anyone touching them.
  • Findings routed to the owning team's normal backlog, not a separate security tool they never open.

Stage 5: verify

A ticket closed as "fixed" is not proof. Verify by re-scanning the asset after the change and confirming the finding disappeared from the running system, not just from the repository. A common gap: the dependency was updated in the main branch, but the service in production still runs an image built a month ago.

# Example: confirm the running image no longer has the CVE
trivy image --severity HIGH,CRITICAL registry.example.com/billing-api@sha256:3f1c... \
  | grep CVE-2026-12345 || echo "not present"

Stage 6: report and improve

Report a few metrics monthly, to engineering leadership as well as security:

  • Mean time to remediate by priority, compared with the SLA.
  • SLA compliance: share of P1 and P2 findings fixed within their deadline.
  • Open exploitable findings: count of KEV or high-EPSS findings on exposed assets. This is the most honest single number of your exposure.
  • Coverage: share of assets scanned in the last seven days.
  • Risk acceptances open and expiring.

Avoid reporting the total number of findings as a headline. It mostly measures how many scanners you run.

Common mistakes

Mistake Why it hurts Do this instead
Prioritising by CVSS alone Hundreds of "criticals", real threats buried Combine KEV, EPSS, exposure and data sensitivity
Scanning only repositories Running systems drift from code Scan registries and running workloads too
Separate security backlog Engineers never see it Route findings to team backlogs with SLAs
Closing tickets without re-scanning Fixes that never deployed Verify on the running asset
Permanent exceptions Risk accumulates silently Acceptances expire and are re-reviewed

Key takeaways

  • Vulnerability management is a continuous lifecycle, starting with knowing your assets and owners.
  • Scan code, images, hosts, infrastructure and cloud configuration, on change and on schedule.
  • Prioritise with exploitation signals (KEV, EPSS) and exposure, not severity alone, and write the rule down.
  • Make remediation cheap with automated updates and regular image rebuilds.
  • Verify fixes on running systems and report time-to-remediate and exploitable exposure.

References