A practical playbook: triage → applicability → exposure → reachability → exploitability → blast radius → business impact → treatment → verification.
When I was working in SecOps, one of the most useful lessons I learned was painfully simple: a red CVSS number or a “High Impact” scanner alert is not, by itself, proof of a real product risk. If the dashboard is screaming 9.8 in bright red, congratulations — you have a signal. The thinking part still belongs to you.
A scanner can tell you that a vulnerability has a CVSS score of 9.8. An SCA tool can flag a vulnerable dependency. SAST can identify a potentially dangerous sink. DAST can confirm behavior in a running application. A CNAPP can surface a cloud misconfiguration. And a bug bounty researcher can send you a PoC and, quite reasonably, ask to get paid for the trouble. :)
That is where the real work starts. The scanner has done its job. Now Security has to do something slightly less glamorous: think.
The job of a security engineer is not to forward a finding to developers with the message, “Critical. Fix ASAP.” and then vanish into Slack like a smoke grenade. The job is to understand what was found, whether it actually applies to your product, whether the vulnerable path is reachable, who can attack it, what privileges an attacker gains, where they can pivot next, what the business can realistically lose, and which response reduces risk fastest.
That is the mindset I want to share here — the part of vulnerability management that begins after the scanner has finished being dramatic.
Let’s take the whole thing apart, piece by piece. No magic. No “the tool said so.” Let’s go.
This article is written for three audiences at once:
- people entering Product Security or AppSec who want to understand the full process from scanner alert to decision;
- Senior Security Engineers who need to own triage and risk decisions, not just detection;
- Technical Leads and Security Leads who need a repeatable vulnerability-management process that engineering and business stakeholders can trust.
The core idea is straightforward:
CVSS is a severity signal. Remediation priority appears only after you add context.
That context is not one extra Jira field. It is a chain of evidence: applicability, exposure, reachability, exploitability, threat intelligence, asset criticality, blast radius, compensating controls, business impact, and confidence.
DISCLAIMER
The architectures and some scenarios in this article are intentionally simplified as teaching models. Real CVEs and vendor statements are called out separately. A reachability verdict should never be based on one scanner signal alone: the quality of the decision depends on the evidence you have and how well you understand the actual runtime environment.
Field card: the triage map I would keep in front of me
If I had to compress the entire mindset into one screen, this is the card I would use:
FINDING | v [Real / Valid?] | v [Affected in our build / runtime?] | v [Where is it deployed?] | v [Who can reach it?] | v [Is the vulnerable path reachable?] | v [Possible -> Feasible -> Weaponized -> Active?] | v [What identity / privileges does compromise give?] | v [What can the attacker reach next?] | v [Business impact / blast radius] | v [Which control can break the path now?] | v [Priority -> Treatment -> Owner -> SLA / Expiry] | v [Verify -> Reassess -> Reopen if assumptions change] This is not a checklist created so we can admire another checklist. It is a compact reasoning model — adapt it to your stack, architecture, product, and trust model. If I cannot answer one of these questions, I write Unknown. Security gets weird very quickly when uncertainty is magically rebranded as Low risk just because nobody had time to investigate it.
Another useful operational signal from real programs is age / time at risk: how long a finding has existed in production and how long the asset has remained exposed. Age does not magically turn a Medium into a Critical, but it matters for queue management, breached SLAs, and accumulated security debt — exactly the kind of thing a Lead or Head eventually has to explain when the backlog starts developing its own ecosystem.
Why the old “CVSS = Priority” model breaks down
Ten years ago, the classic vulnerability-management workflow often looked like this:
Scanner -> CVE -> CVSS -> SLA -> Patch A typical policy might say:
CVSS 9.0-10.0 -> Critical -> fix in 24/48h CVSS 7.0-8.9 -> High -> fix in 7 days CVSS 4.0-6.9 -> Medium -> fix in 30 days CVSS < 4.0 -> Low -> backlog That is beautiful for reporting. Neat colors, neat SLAs, neat executive slides. For actual Product Security, though, it is way too crude. Reality, annoyingly, refuses to fit into four severity buckets — and this is where an expert lens starts earning its keep.
FIRST explicitly states in the CVSS v4.0 guidance that CVSS-B measures severity, not risk. CVSS v4.0 separates Base, Threat, Environmental, and Supplemental metrics, which is effectively an acknowledgement that intrinsic technical severity is only one layer of the decision.
Practical examples:
- A CVSS 9.8 issue in a library that your application never calls may be lower operational priority.
- A CVSS 6.5 authorization flaw on an Internet-facing API that leaks cross-tenant data may be far more dangerous to the business.
- A CVSS 8.x issue on an internal CI/CD controller can become P0 if that controller has signing keys, registry write access, production deployment tokens, and Kubernetes privileges.
- A CVSS 10.0 issue in a package that never makes it into the runtime artifact may turn out to be a false positive or a valid
not_affectedconclusion for that product.
So I prefer to reason about priority as:
Priority = f( Technical Severity, Applicability, Exposure, Reachability, Exploitability, Threat Intelligence, Asset Criticality, Blast Radius, Compensating Controls, Business Impact, Confidence ) This is not an arithmetic formula. Please do not multiply ten subjective coefficients until a magical 8.73 falls out of Excel and suddenly becomes “science.” These are the questions I want answered before I call something Critical for us.
Field vocabulary: the language of practical vulnerability triage
If you are new to Product Security, the terminology can look like someone dropped the alphabet into Jira: CVE, CWE, CVSS, EPSS, KEV, VEX, SCA, SAST, DAST. At first it sounds like corporate magic. It really is not. Each term answers a fairly concrete question, so let’s crack it open.
| Term | What it means in real work |
|---|---|
| Finding | Any security signal: CVE, SAST finding, DAST issue, misconfiguration, pentest finding, bug bounty report |
| CVE | Identifier for a publicly disclosed vulnerability |
| CWE | Weakness class such as SSRF, path traversal, auth bypass, deserialization, etc. |
| CVSS | Standardized technical-severity scoring framework |
| SCA | Software Composition Analysis — identifies vulnerable third-party dependencies |
| SAST | Static Application Security Testing — analyzes source/IR without running the app |
| DAST | Dynamic Application Security Testing — tests a running application |
| SBOM | Software Bill of Materials — inventory of product components |
| VEX | Vulnerability Exploitability eXchange — machine-readable statement about vulnerability applicability/exploitability in a specific product |
| EPSS | FIRST model estimating the probability that a CVE will be exploited in the wild within the next 30 days |
| KEV | CISA Known Exploited Vulnerabilities Catalog — vulnerabilities with confirmed exploitation in the wild |
| PoC | Proof-of-concept exploit or exploitation demonstration |
| Pre-auth / unauth | Exploitable before authentication / without an account |
| Exposure | How reachable the system is: Internet, partner, VPN, internal, namespace-local, offline |
| Reachability | Whether attacker-controlled execution/data can reach the vulnerable code/function/sink |
| Entry point | HTTP route, queue consumer, RPC method, webhook, file parser, CLI command, etc. |
| Sink | Dangerous operation: command execution, SQL query, deserialization, file write, outbound request, etc. |
| Source-to-sink | Flow of untrusted input into a dangerous sink |
| Call graph | Graph of function calls used for code-level reachability analysis |
| Taint flow | Tracking untrusted data through the program |
| Attack path | Practical path from attacker entry point to security impact |
| Exploit chain | Multiple primitives or vulnerabilities chained into one scenario |
| Blast radius | Scope of damage after successful compromise |
| Pivot / lateral movement | Moving from one compromised system to additional systems |
| Crown jewel | Asset with outsized value: CI/CD, IAM, secrets, signing infrastructure, production control plane |
| Compensating control | Control that genuinely breaks an attack path or reduces impact when a direct fix is not immediate |
| Residual risk | Risk that remains after mitigation |
| Risk acceptance / exception | Formal temporary acceptance with owner, rationale, evidence, and expiration |
| SLA | Agreed remediation timeline for a risk class |
| ASPM | Application Security Posture Management — correlation of AppSec signals and context |
| CNAPP / CSPM | Cloud-native/cloud posture tooling that adds cloud and identity context |
Memorizing the acronym is not the goal. You do not get bonus points for saying VEX three times in a meeting. Knowing which decision the signal helps you make is what matters.
My engineering mindset: do not “close a CVE”; prove the risk
When I investigated a vulnerability, I tried not to begin with:
“We have a Critical. We need a dev fix. Put it in the sprint.”
That sentence is usually where useful context goes to die.
I started with a different question — usually to myself first, and then to the SecOps / engineering folks who actually knew the system:
“What exactly has to happen for an attacker to turn this finding into real impact?”
That question changes the quality of the conversation immediately.
Instead of a red card in a dashboard, you build an attack story:
Actor -> Entry point -> Trust boundary -> Vulnerable component/function -> Required privileges/configuration -> Exploit primitive -> Initial impact -> Pivot -> Crown jewel / data / production -> Business consequence If I cannot sketch that chain, triage is probably not finished. I may have a finding, but I do not yet have a defensible risk story. So I keep digging: more evidence, more docs, more conversations — and these days, yes, sometimes I will bounce the reasoning off an AI assistant too. The point is not who you ask. The point is not stopping at the first red number.
Eight questions I ask before assigning priority
This is the part where the finding stops being a colored rectangle and starts becoming an engineering problem.
1. Is the finding real?
Start with a sanity check:
- Is the package and version correct?
- Is the SBOM stale?
- Is the fingerprint a false positive?
- Does the binary or image that actually runs fall into the affected range?
- Does the OS/distro/build matter?
- Is this a vendor-patched package that retained an upstream-looking version string?
- Is the dependency present only in the build stage but absent from the runtime image?
Bad triage starts when a team spends two days emergency-patching a CVE that is not even present in the production artifact. Few things build character faster than discovering that fact after the rollback meeting.
2. Where does the component actually live?
Next, establish deployment context.
repo -> build -> image -> registry -> Kubernetes -> pod -> service -> route You need to know:
- dev, test, staging, or production;
- public or internal;
- customer-facing or back-office;
- container, VM, serverless, or bare metal;
- ephemeral worker or long-lived controller;
- whether it holds state, secrets, or customer data;
- which identities and service accounts are attached to it.
This is where asset criticality begins. And yes, man, having a reasonably clear map of the product and infrastructure in your head helps a lot.
3. What is the exposure?
Exposure is more nuanced than “is port 443 on the Internet?”
A useful mental model is:
Internet unauthenticated ↓ Internet authenticated ↓ Partner / customer private network ↓ VPN / corporate network ↓ Internal service-to-service ↓ Namespace / host local ↓ Offline / build-only An internal system can be more dangerous than a public endpoint if it has powerful downstream privileges.
A Jenkins controller might be VPN-only but still hold:
- GitLab tokens;
- kubeconfig credentials;
- registry credentials;
- cloud roles;
- artifact signing keys.
“Internal only” is not an automatic downgrade. Internal is a network location, not a security control. A CI server behind VPN with production credentials can be far more interesting to an attacker than a boring public brochure site.
4. Is the vulnerable path reachable?
Write this one down: package presence is not exploitability. For vulnerability triage, that is close to an axiom. This is where modern SCA and Product Security get much more interesting.
An SBOM tells me the package is in the room; it does not prove the package committed the crime. Use the evidence ladder below as a cheat sheet.
I like to think in terms of an evidence ladder.
Reachability evidence ladder
| Level | Evidence | What we actually know |
|---|---|---|
| L0 — Presence | package/version appears in SBOM | component exists |
| L1 — Imported/Linked | application imports or links it | dependency is used somewhere |
| L2 — Function reachability | call graph reaches affected symbol | vulnerable function may be invoked |
| L3 — Source-to-sink | taint/data-flow from attacker input to vulnerable sink | attacker-controlled data may reach the operation |
| L4 — Route/exposure | entry point is published through trust boundaries | attacker can initiate the flow |
| L5 — Runtime evidence | tracing, coverage, eBPF, telemetry confirms execution path | path has executed in the deployed environment |
The higher the level of evidence, the stronger the conclusion. Package present and attacker-controlled input reached the affected function in runtime are not the same caliber of evidence, even if a dashboard puts both findings in the same red box.
But the reverse matters too: failing to find a static path does not always prove the code is impossible to reach. Reflection, dynamic loading, plugin systems, native calls, dependency internals, and runtime code generation can defeat simplistic call-graph analysis.
So a mature verdict is not:
NOT REACHABLE = SAFE
It is closer to:
No reachable path found with current evidence; confidence: Medium. Reassess if feature/configuration/runtime changes.
Tooling is moving in this direction. OSV-Scanner supports call analysis and can distinguish vulnerabilities affecting called code from findings where the affected code path was not observed. OWASP dep-scan also supports reachability-aware prioritization and VEX-oriented workflows.
5. How weaponizable is the issue?
Technical severity is not enough. That is another one worth burning into memory. A scary score without a workable exploit path and a modest score with a cheap, automatable attack are having very different days.
I look at:
- public PoC availability;
- whether a functional exploit exists;
- exploit reliability;
- required prerequisites;
- pre-auth vs authenticated exploitation;
- whether mass scanning is easy to automate;
- CISA KEV status;
- EPSS probability and percentile;
- vendor warnings about active exploitation;
- threat-intelligence reporting.
EPSS: probability != percentile
There is one little trap here that trips up people who already work with vulnerability data: EPSS probability and EPSS percentile are not the same thing. Please do not mix them. The labels look close enough to cause trouble, and they mean very different things.
- Probability is the model-estimated probability that exploitation will be observed in the next 30 days. A value of
0.10means roughly 10%, not “0.1 out of 10.” - Percentile is the relative rank of that probability across the CVEs currently scored by FIRST. A percentile of
0.95means the score is higher than roughly 95% of the current population — not a 95% probability of exploitation.
Because most CVEs are never observed being exploited, the distribution is heavily skewed toward zero. That means a probability that looks small at first glance can still sit surprisingly high in the percentile ranking.
I like to keep both values together:
EPSS probability: 0.10 (10%) EPSS percentile: 0.95 (95th percentile) EPSS checked_at: YYYY-MM-DD checked_at matters because EPSS is updated daily. Threat signal is not a tattoo on the CVE. It changes.
And one more rule: do not multiply EPSS by CVSS and call the result a risk score. You will get a very impressive-looking number and no clear statistical meaning. Pretty math is still pretty math.
A useful distinction:
Possible — theoretically exploitable.
Feasible — practically exploitable.
Weaponized — working exploit/tooling is available.
Active — exploitation is happening in the wild.
Those states deserve different operational responses. Going deeper into exploit lifecycle and threat modeling would be another article, so I am not turning this one into a security encyclopedia.
6. What does the attacker get after the first step?
This is privilege context.
RCE in a pod does not automatically mean cluster compromise. RCE is not a teleport spell to cluster-admin. Context still matters.
Check:
- UID/root vs non-root;
- privileged container status;
- Linux capabilities;
- hostNetwork / hostPID / hostPath;
- service account token;
- Kubernetes RBAC;
- cloud IAM role;
- mounted secrets;
- metadata-service access;
- outbound egress;
- control-plane access;
- registry permissions;
- CI/CD credentials.
These are very different incidents:
RCE in an isolated non-root pod versus
RCE in a privileged deployment controller with cluster-admin The primitive is similar. The risk is not. This is also one of those moments where you can usually tell who is Junior, who is Mid, and who has already been through enough production fires to think like a Senior.
7. What is the blast radius?
After initial access, I ask:
“What can the attacker do next?”
The first shell is often the least interesting part of the story.
Example:
Internet | v [Nginx] | v [API Pod] | +--> service account | +--> internal DB | +--> internal admin API | +--> container registry | +--> secrets A vulnerability that causes a local DoS in one stateless replica is one risk.
A vulnerability that opens a path to:
- cross-tenant data;
- production secrets;
- signing keys;
- build infrastructure;
- registry write access;
- IAM privilege escalation;
- production deployment;
- customer PII;
is something else entirely. That is blast-radius analysis. And when those downstream paths include crown jewels, yes — the priority can jump fast.
8. What is the fastest way to break the attack path?
A critical mindset shift: remediation is not synonymous with patching. Patch is a treatment option, not a religion.
Patching is often the OK answer, but it is not the only answer.
The fastest risk reduction may be:
- disable a feature flag;
- remove a route;
- remove Internet exposure;
- disable a CLI;
- deploy a WAF/gateway rule;
- block egress;
- deny access to metadata endpoints;
- reduce IAM/RBAC;
- remove a secret mount;
- isolate a namespace;
- roll back a component;
- remove a vulnerable package;
- rotate credentials;
- deploy a vendor hotfix;
- temporarily take a node or service out of rotation.
Then you perform the permanent fix safely. If you can break the attack path in 15 minutes and the permanent code change takes three days, there is no medal for leaving the door open while everyone waits for the elegant fix.
A strong Security Engineer influences not only what gets fixed, but how risk is reduced right now.
The full processing chain: from finding to decision
If I were onboarding a junior engineer, this is the workflow I would give them. Keep it as a cheat sheet until the sequence becomes muscle memory.
[SCA / SAST / DAST / CNAPP / Pentest / Bug Bounty / Vendor Advisory] | v 1. Intake / Normalize | v 2. Validate / Dedupe | v 3. Applicability | v 4. Exposure / Reachability | v 5. Exploitability / Threat Intel | v 6. Asset / Identity / Blast Radius | v 7. Business Impact | v 8. Compensating Controls | v 9. Priority Decision | v 10. Treatment / Ownership | v 11. Fix Verification | v 12. Evidence / Exception / Recheck And this is the purpose of each stage:
| Stage | Main question | Typical output |
|---|---|---|
| Intake | Where did the signal come from? | finding ID, source, asset |
| Validate | Is it real and unique? | confirmed / duplicate / false positive |
| Applicability | Is our version/build/runtime affected? | affected / not affected / unknown |
| Exposure | Who can reach it? | Internet / auth / VPN / internal |
| Reachability | Can execution/data reach the affected path? | route, call path, taint evidence |
| Exploitability | Can this be exploited realistically today? | PoC, EPSS, KEV, active exploitation |
| Blast radius | What happens after compromise? | identities, secrets, downstream systems |
| Business impact | What does the business lose? | data, money, uptime, compliance, trust |
| Controls | What breaks the chain? | WAF, segmentation, IAM, disabled feature |
| Priority | How quickly do we need to act? | P0/P1/P2/P3 or Critical/High/Medium/Low |
| Treatment | What are we doing? | patch, mitigate, contain, accept, avoid |
| Verification | Did we actually reduce risk? | retest / scan / telemetry evidence |
| Recheck | What could invalidate the decision? | expiration, reopen triggers |
What a good triage ticket looks like
One of the most valuable Senior Engineer skills is turning noisy scanner output into a concise, decision-ready record.
A template I would use looks like this:
Finding ID: Source: SCA / SAST / DAST / Pentest / Vendor / Bug Bounty Asset / Service: Environment: prod / staging / dev Owner: CVE / CWE: Affected component + version: CVSS-B / vector: EPSS probability / percentile: CISA KEV: yes/no Public PoC: yes/no/unknown Active exploitation: yes/no/unknown Exposure: Authentication required: Entry point: Reachability evidence: Exploit prerequisites: Assessment confidence: High / Medium / Low Runtime identity: IAM / RBAC context: Secrets available: Downstream systems: Blast radius: Data classification: Existing controls: Control verification evidence: Residual risk: Final priority: Recommended treatment: Security owner: Engineering owner: Due date / SLA: Exception expiry: Reopen triggers: Compare two handoffs.
Weak handoff
Critical CVE 9.8. Please patch ASAP.
Strong handoff
CVE-Xis present in production imageorders-api:v312. The package version is affected, but the vulnerable XML import function is not reachable from published routes; the feature is disabled, no call-graph path was found, and runtime tracing did not observe parser loading. The service is Internet-facing, but the affected route is not exposed. The CVE is not in KEV and no reliable public exploit was identified. Hypothetical impact is high because the service processes customer data. Priority: P2. Upgrade in the next release train. Record VEXnot_affected/code_not_reachablewith a 30-day expiration. Reopen if the import feature is enabled, the route is published, a reliable exploit appears, KEV status changes, or architecture changes.
The second version gives a Tech Lead something they can actually decide on. The first one was written by a junior, a bot, somebody already mentally at lunch — or something else is going on, and none of those options are especially comforting. :)
Severity, Risk, and Priority are different concepts
This distinction comes up constantly in interviews and real-world work — mostly because dashboards, tickets, and people love using these words as if they were interchangeable. They are not.
Severity
How serious the technical impact could be. CVSS is primarily here.
Risk
Likelihood + impact in the context of your organization.
Priority
How quickly you should act relative to other risks, delivery constraints, and available resources.
You can absolutely have:
High Severity + Low Reachability + Strong Controls = Medium Priority or:
Medium Severity + Internet Exposure + High Exploitability + Crown Jewel Impact = Critical Priority That is not inconsistency. That is the entire point of context-aware prioritization.
Risk Priority != Remediation Difficulty
This is a Lead-level nuance that tends to disappear the moment Security and Engineering start negotiating timelines and someone discovers the fix is inconvenient.
A vulnerability does not become less risky just because it is difficult to fix. Hard to fix is not a severity metric. Remediation difficulty changes the treatment plan; it should not retroactively rewrite the assessment.
I keep at least five dimensions separate:
| Dimension | Question |
|---|---|
| Risk / Priority | How dangerous is this, and how urgently must we reduce exposure? |
| Engineering effort | How much work does the permanent fix require? |
| Change / availability risk | Could remediation break production, create regressions, or cause downtime? |
| Fix availability | Is there a patch/upgrade/hotfix, or do we need our own redesign? |
| Time at risk | How long has the asset already remained exposed? |
That creates a much more honest decision model:
| Low remediation effort | High remediation effort / change risk | |
|---|---|---|
| High risk / high priority | Fix now. This is the ideal P0/P1 quick win. | Contain now, engineer the permanent fix next. Remove exposure, disable the feature, reduce privilege, isolate, add a verified control, then patch or redesign safely. |
| Lower risk / lower priority | Take the quick win when it is almost free and unlikely to create regression. | Plan, temporarily accept, or redesign with an owner, rationale, expiry, and reopen triggers. |
The dangerous anti-pattern sounds like this:
“Yes, it is a P0, but the patch is hard, so let’s call the finding Medium.”
No. If the risk is truly P0, it remains P0. The immediate response may simply be containment / mitigation rather than patch: break the attack path quickly while Engineering prepares a safe permanent fix.
The reverse matters too. An easy fix does not automatically become P0. It may be a great quick win, but remediation convenience is not a measure of risk.
If you can explain this distinction cleanly in a technical interview, congratulations — somewhere, spiritually, another 15% just appeared in your compensation negotiation. :)
A practical decision matrix
No single row makes the decision for you. The matrix exists to make reasoning visible — not to outsource judgment to colored cells.
I prefer transparent qualitative reasoning over spreadsheet numerology. A clear High because X, Y, and Z is often more useful than a mysterious 8.736 produced by a formula nobody remembers writing. Seriously, what the hell is 8.736 supposed to communicate to a human being? We are engineers, not numerology enthusiasts. Say what the risk is and why.
| Signal | Low | Medium | High | Critical signal |
|---|---|---|---|---|
| Severity | limited impact | material impact | major C/I/A | RCE, auth bypass, systemic compromise |
| Exposure | offline/local | internal | customer/partner reachable | Internet unauthenticated |
| Reachability | proven unreachable | uncertain | statically reachable | input-to-sink / runtime confirmed |
| Exploit maturity | theoretical | PoC with constraints | functional exploit | KEV / active exploitation |
| Asset criticality | disposable | supporting internal | important production | IAM, CI/CD, secrets, control plane |
| Blast radius | isolated | one service | several systems/data | supply chain / cross-tenant / org-wide |
| Controls | strong and verified | partial | weak | absent/bypassable |
| Confidence | weak evidence | mixed | strong | multiple independent evidence sources |
Then map to an operational class.
P0 / Critical
Typical profile:
- Internet-facing or easy lateral reach;
- pre-auth / unauthenticated;
- vulnerable path is reachable;
- KEV / active exploitation / reliable weaponized exploit;
- crown-jewel asset;
- large blast radius;
- no effective controls.
Action: immediate containment, emergency remediation lane, incident-response assumptions where justified, credential rotation, IOC review.
P1 / High
- exploit path is real;
- asset is important;
- exploitation is feasible;
- impact is high;
- but active exploitation is not confirmed or partial mitigation exists.
Action: expedited fix, short SLA, named owner, active tracking.
P2 / Medium
- vulnerability is applicable;
- exploitation requires authentication, configuration, or another prerequisite;
- blast radius is limited;
- controls are working.
Action: normal engineering cycle with a concrete date.
P3 / Low / Temporarily Accepted
- component is present but affected function is unreachable with strong evidence;
- feature is disabled;
- required configuration does not exist;
- exposure is minimal;
- compensating controls are verified.
Action: documented exception/risk acceptance with owner, expiration, and reopen conditions.
A risk acceptance without an expiration date is not risk management. It is archiving a problem.
Where CVSS v4, EPSS, KEV, SSVC, and VEX fit — and how the hell do you remember all of this?
These signals do not compete. They answer different questions. And if you forget one acronym but remember what the other two mean, you are fine. Understanding beats acronym bingo. Nobody has banned cheat sheets. When I taught at university, I let students use their notes during exams — because memorization and understanding are not the same skill.
| Framework / Signal | Question it answers |
|---|---|
| CVSS-B | How severe is the vulnerability technically, in isolation? |
| CVSS Threat metrics | What is the current exploit-maturity/threat state? |
| CVSS Environmental | How does this deployment/environment alter severity context? |
| EPSS | What is the estimated probability of observed exploitation over the next 30 days? |
| CISA KEV | Has exploitation in the wild been confirmed? |
| SSVC | What response decision should we make through a decision tree? |
| VEX | Is a specific product affected, and how do we communicate that status? |
CISA’s SSVC is useful as a mindset because it focuses on a decision tree, not score worship. CISA’s model uses outcomes such as Track, Track*, Attend, and Act.
I am not suggesting every company copy SSVC exactly. The important idea is that the purpose of assessment is to make a response decision, not produce a pretty number.
Case 1: CVSS 9.8, but the attack path is not available
Consider this production platform. The architecture is intentionally anonymized; some details stay behind the NDA, but the reasoning is the part that matters:
Internet | v [Cloud LB] | v [Nginx Ingress] | v [Istio Gateway] | v +-------------------+ | orders-api Pod | | app + sidecar | +-------------------+ | | v v [PostgreSQL] [Kafka/MQ] CI/CD: Developer -> GitLab -> Jenkins -> Registry -> Kubernetes SCA finds a vulnerable XML-parsing dependency with CVSS 9.8.
The dashboard turns red. The default SLA says “Critical.”
What an inexperienced engineer does
Creates a Jira ticket:
Upgrade immediately. CVSS 9.8.
Technically a ticket. Spiritually, an automated email with anxiety attached.
What a Senior Product Security Engineer does
Checks applicability and path evidence.
- The package is genuinely present in the runtime image.
- The affected function is used only by a legacy XML-import module.
- The
xml_import=falsefeature flag is set in production. - The import route is absent from Nginx/Istio configuration.
- Static call-graph analysis does not find a path from HTTP handlers to the affected parser.
- Runtime tracing does not show the parser loading during the observed period.
- NetworkPolicy is not the main justification, but it further limits lateral movement.
- No KEV or active-exploitation signal currently invalidates those assumptions.
Reachability verdict
Package present: YES Affected version: YES Imported: YES Affected function: NOT REACHED by current static evidence Published route: NO Feature enabled: NO Runtime observed path: NO Confidence: MEDIUM-HIGH Decision
Technical severity remains Critical. We are not arguing with the vulnerability; we are separating intrinsic severity from our current exposure and exploitability.
Operational priority may reasonably be P2, because the deployed product currently offers no attacker path to the affected function. But I would not close and forget.
I would:
- upgrade the dependency in the next release train;
- issue a VEX
not_affectedstatement with a justification such ascode_not_reachable, when supported by the VEX format in use; - retain the evidence;
- assign an expiration date;
- define automatic reopen triggers.
For example:
Reopen if: - XML import feature is enabled; - route is published; - dependency behavior changes; - reliable exploit changes assumptions; - CVE enters KEV; - architecture / call path changes. Lesson: we did not “ignore a Critical.” We made a bounded, evidence-based exploitability conclusion with explicit confidence and reassessment conditions.
Case 2: no direct RCE, but priority becomes Critical
Now the opposite scenario. This one is worth remembering because it is exactly where “but it is not RCE” thinking gets expensive.
Internet | v [CDN / WAF] | v [Nginx Ingress] | v [image-preview API] | | outbound fetch v [internal/cloud endpoints] | +--> metadata / workload identity (if reachable) +--> internal admin services +--> storage +--> registry The preview function contains an SSRF weakness.
At first glance:
- there is no direct RCE;
- the endpoint “just downloads an image”;
- the scanner might rate it Medium/High;
- engineering wants to put it into the normal backlog.
Exposure
- Internet-facing;
- no account required;
- requests are easy to automate.
Reachability
- attacker controls the URL;
- URL reaches the outbound HTTP client;
- egress is unrestricted;
- there is no destination allowlist.
Identity context
The workload has a cloud identity and can access internal resources.
Blast radius
A successful SSRF may become:
Internet actor -> SSRF primitive -> internal/cloud endpoint access -> credentials / internal service access -> data or registry compromise Response
The code needs to be fixed, but fast containment may come first. This is where “just patch it” stops being strategy and starts being wishful thinking:
- disable preview;
- add an outbound allowlist;
- block link-local/metadata destinations;
- enforce egress NetworkPolicy;
- reduce workload IAM;
- move the fetcher into a more isolated workload;
- add authentication/rate limiting where product design supports it;
- then implement the permanent code fix.
Outcome
Technical severity: Medium/High Internet exposure: Critical signal Reachability: Confirmed Exploit automation: High Business impact: High/Critical Controls: Weak Priority: P0/P1 Lesson: absence of RCE does not make a vulnerability low risk. In Product Security, exploit chains matter more than CWE labels.
Case 3: TeamCity CVE-2023-42793 — when nearly every signal points in the same direction
Now a real-world example. In 2023, JetBrains disclosed CVE-2023-42793 in TeamCity On-Premises. Google it if you want the ugly details. The vendor described a scenario in which an unauthenticated attacker with HTTP(S) access could achieve RCE and administrative control of the TeamCity server. JetBrains shipped a fix in 2023.05.4 and provided a security patch plugin for older versions. Nasty bug. Great case study. Probably considerably less fun if you were the team responsible for TeamCity that week.
JetBrains later reported observed exploitation, including nation-state activity. CISA added the vulnerability to KEV.
Imagine this enterprise deployment:
Internet / Corp Network | v [TeamCity Server] | +----> GitLab repos | +----> Build agents | +----> Docker Registry | +----> Artifact repository | +----> Kubernetes deploy token | +----> Cloud secrets / IAM Contextual signals:
- Severity: Critical;
- Exposure: Critical if public, still material if internal;
- Reachability: direct HTTP(S);
- Auth requirement: pre-auth/unauthenticated in the vulnerable scenario;
- Threat intelligence: active exploitation;
- KEV: yes;
- Asset criticality: CI/CD crown jewel;
- Blast radius: source + artifacts + deployments + secrets;
- Supply-chain impact: possible downstream compromise.
At that point, arguing whether the score should be 9.8 or 10.0 has roughly the same operational value as debating the font while the building alarm is going off.
Strong response
- Contain — remove public exposure or restrict access if patching is not immediate.
- Patch/upgrade — apply the vendor fix.
- Hunt — inspect IOCs, logs, and abnormal behavior.
- Assess secret exposure — determine which credentials were available to TeamCity.
- Rotate — deployment tokens, registry credentials, and cloud credentials where justified.
- Inspect downstream systems — build agents, artifacts, and releases.
- Restore — only after verification.
Lesson: mature vulnerability management sometimes has to transition directly into incident-response mode.
Three more real-world anchors
Jenkins CVE-2024-23897
Jenkins disclosed an arbitrary file-read issue through the CLI that could lead to more serious outcomes, including RCE scenarios under specific conditions. The vendor documented prerequisites and a workaround: disabling CLI access could prevent exploitation while teams prepared an upgrade.
What this teaches:
- the label “file read” can understate downstream impact;
- prerequisites matter;
- configuration and runtime details can materially change exploitability;
- temporary mitigation can be the correct first response while patching is underway.
XZ Utils CVE-2024-3094
The XZ backdoor illustrated another side of contextual analysis: a major industry event does not mean every Linux system is affected equally.
Teams h...



