Every team has more vulnerabilities than time. The difference between teams that manage vulnerabilities well and those that drown in CVEs is not tooling — it is triage, prioritisation, and a system that keeps the backlog from growing faster than you can fix it.
The vulnerability lifecycle
Discovery → Triage → Prioritisation → Remediation → Verification → Closure
Discovery
Vulnerabilities arrive from multiple sources:
| Source | Examples |
|---|---|
| Dependency scanners | Dependabot, Snyk, Grype, Trivy |
| SAST | Semgrep, CodeQL, SonarQube |
| DAST | OWASP ZAP, Burp Suite (automated scans) |
| Container scanners | Trivy, Grype, Snyk Container |
| IaC scanners | Checkov, tfsec, KICS |
| Penetration tests | Scheduled external / internal tests |
| Bug bounty | HackerOne, Bugcrowd reports |
| Vendor advisories | NVD, GitHub Security Advisories, vendor mailing lists |
| Incident follow-up | Vulnerabilities found during incident investigation |
The challenge is not finding vulnerabilities. It is managing the volume.
Triage
Triage answers one question: does this vulnerability actually affect us?
Not every CVE applies
A scanner reports a critical CVE in libxml2. Before panicking:
- Is the vulnerable function reachable? If your code never calls the affected parsing function, the risk is lower.
- Is the vulnerable component exposed? A library vulnerability in a container that has no network access is different from one in a public-facing API.
- Are there mitigating controls? Input validation, WAF rules, or sandboxing may reduce exploitability.
- Is there a known exploit? A CVE with a public exploit is more urgent than one that is theoretical.
Reachability analysis
Modern SCA tools can determine whether the vulnerable function is actually called in your code:
# Example Snyk output with reachability
SNYK-JS-LODASH-1234567
Severity: High (CVSS 7.4)
Reachable: No — vulnerable function _.template() is not called
Recommendation: Upgrade when convenient, not urgent
If the vulnerable code path is not reachable, downgrade the priority. If it is reachable and exploitable, treat it as urgent.
Prioritisation
CVSS is not enough
CVSS scores tell you the theoretical severity of a vulnerability. They do not tell you:
- Whether it is exploitable in your specific environment
- Whether there are compensating controls
- What the business impact of exploitation would be
A CVSS 9.8 in an internal tool used by 3 people may be less urgent than a CVSS 6.5 in a payment processing API.
A practical prioritisation framework
Score each vulnerability on three dimensions:
| Factor | High | Medium | Low |
|---|---|---|---|
| Exploitability | Public exploit exists, easy to execute | PoC exists, requires some skill | Theoretical, no known exploit |
| Exposure | Internet-facing, handles untrusted input | Internal but accessible to many users | Isolated, no network exposure |
| Impact | Customer data, authentication, financial | Internal data, service availability | Cosmetic, informational |
Priority mapping:
| Combination | Priority | SLA |
|---|---|---|
| High exploitability + High exposure + Any impact | P1 — Critical | Fix within 24 hours |
| High exploitability + Medium exposure | P2 — High | Fix within 7 days |
| Medium exploitability + High exposure | P2 — High | Fix within 7 days |
| Medium + Medium | P3 — Medium | Fix within 30 days |
| Low exploitability or Low exposure | P4 — Low | Fix within 90 days |
| Not reachable / fully mitigated | Accept risk | Document and revisit quarterly |
EPSS and KEV
Supplement CVSS with data-driven signals:
- EPSS (Exploit Prediction Scoring System): Probability that a CVE will be exploited in the wild in the next 30 days. A vulnerability with CVSS 7.0 but EPSS 0.95 is far more urgent than one with CVSS 9.0 and EPSS 0.01.
- CISA KEV (Known Exploited Vulnerabilities): A list of CVEs confirmed to be actively exploited. If a vulnerability is on the KEV list, it is priority 1 regardless of CVSS.
Remediation strategies
Upgrade the dependency
The most common fix. Use automated tools to create pull requests:
# Dependabot configuration
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
groups:
security:
applies-to: security-updates
Apply a patch
When an upgrade is not possible (breaking changes, no fix available):
- Use
npm override/pip constraint/maven exclusionto force a patched version of a transitive dependency - Apply a vendor patch if provided
- Use a temporary fork with the fix (document this — forks drift)
Mitigate with controls
When you cannot upgrade or patch immediately:
| Mitigation | Effect |
|---|---|
| WAF rule | Block known exploit payloads at the edge |
| Input validation | Prevent malicious input from reaching the vulnerable code |
| Network policy | Restrict access to the affected service |
| Feature flag | Disable the affected feature temporarily |
| Rate limiting | Slow down exploitation attempts |
Document mitigations as temporary measures with a deadline to fix properly.
Accept the risk
Some vulnerabilities are genuinely low risk. Accepting risk is a valid decision when:
- The vulnerability is not reachable
- Compensating controls fully mitigate it
- The effort to fix exceeds the risk
Document the acceptance:
## Risk Acceptance: CVE-2025-12345
**Component:** libfoo 2.3.1
**CVSS:** 6.5 | **EPSS:** 0.02 | **KEV:** No
**Reachable:** No — affected function not called
**Mitigations:** Component runs in isolated container with no network access
**Decision:** Accept until next major version upgrade (Q3 2025)
**Reviewed by:** Alice (Security), Bob (Engineering Lead)
**Review date:** 2025-03-15
Tracking and SLAs
Define remediation SLAs
| Priority | SLA | Escalation |
|---|---|---|
| P1 — Critical | 24 hours | Auto-escalate to engineering lead after 12h |
| P2 — High | 7 days | Auto-escalate after 5 days |
| P3 — Medium | 30 days | Flag in sprint planning |
| P4 — Low | 90 days | Batch with other maintenance |
Metrics to track
| Metric | Purpose |
|---|---|
| Mean time to remediate (MTTR) by severity | Are you meeting SLAs? |
| Vulnerability backlog age | Are old vulns piling up? |
| SLA compliance rate | What percentage of vulns are fixed within SLA? |
| Reopened vulnerabilities | Are fixes actually working? |
| Vulnerability introduction rate | Are you creating new vulns faster than fixing old ones? |
Track the ratio of new vulnerabilities introduced vs vulnerabilities fixed per sprint. If the line is trending up, you have a systemic problem.
Dependency update strategy
Keep dependencies current
The single most effective vulnerability management practice is keeping dependencies up to date. Teams that fall behind on updates accumulate a backlog of vulnerabilities plus breaking changes, making each update harder.
Recommended cadence:
- Security updates: same day or next business day (automated merge if tests pass)
- Patch versions: weekly (automated)
- Minor versions: bi-weekly (automated with CI verification)
- Major versions: monthly review, scheduled upgrade sprint
Automated merge policies
# Example: auto-merge patch-level security updates
- match:
dependency-type: production
update-type: semver:patch
auto-merge: true
required-checks:
- unit-tests
- integration-tests
- security-scan
Scanner configuration
Reduce noise from scanners
| Technique | How |
|---|---|
| Ignore dev dependencies for production scans | trivy --vuln-type os,library --severity HIGH,CRITICAL |
| Suppress known false positives | .trivyignore, .snyk policy files |
| Focus on reachable vulnerabilities | Enable reachability analysis in Snyk/Semgrep |
| Group related CVEs | One ticket for "upgrade express" not 5 separate CVE tickets |
Example Trivy ignore file
# .trivyignore
# CVE not reachable — see risk acceptance RA-2025-042
CVE-2025-12345
# Fixed in next scheduled major upgrade (Q3 2025)
CVE-2025-67890
Summary
Vulnerability management is a system, not a scan. Discover vulnerabilities from multiple sources, triage for reachability and actual risk, prioritise using exploitability, exposure, and impact — not just CVSS. Use EPSS and KEV data to focus on what is actually being exploited. Define remediation SLAs and track compliance. Keep dependencies current to prevent backlog accumulation. Accept risk explicitly when appropriate, and document every decision.
