Skip to content
Private Beta ·invite-only access. Reach out to get in.
Back to blog
Education7 min read·

VEX Explained: How to Declare Which CVEs Don't Affect You

VEX is the machine-readable way to say a CVE does not affect your product. Four statuses, five justifications, and the internal use case most teams miss entirely.

Your scanner reports 47 vulnerabilities in the container image. You investigate. Forty-one of them are in code paths your application never executes, in a library that ships a vulnerable function you do not call.

You write this in a Slack thread. Six weeks later the same 41 findings appear in the next scan, and somebody investigates them again.

VEX exists to stop that.

The Problem VEX Solves

VEX stands for Vulnerability Exploitability eXchange. It emerged from the CISA-convened SBOM community work, and the canonical starting point is CISA's document Minimum Requirements for Vulnerability Exploitability eXchange (VEX).

The problem it addresses is precise: presence is not exploitability. A dependency graph tells you a vulnerable component is in the build. It says nothing about whether the vulnerable code is reachable, whether it is compiled in, or whether a mitigation already neutralises it. Almost all of the noise in modern dependency scanning lives in that gap.

VEX Is Not an SBOM, and It Is Not a Scan Report

Three artefacts, three jobs:

  • An SBOM says what is present. Our SBOM guide covers generation and formats, and stops exactly where this article starts.
  • A scan report says what matched between that inventory and a vulnerability database.
  • A VEX statement says what that match means, as an assertion by someone who actually knows the code.

VEX is the only one of the three that carries a judgement, which is why it is the only one that can reduce work rather than create it.

The Four Statuses

StatusMeaning
NOT AFFECTEDNo remediation is required for this vulnerability in this product
AFFECTEDActions are recommended to remediate or address this vulnerability
FIXEDThis product version contains a fix for the vulnerability
UNDER INVESTIGATIONIt is not yet known whether this product is affected

UNDER INVESTIGATION is a legitimate, publishable state. Teams treat it as an admission of failure and stay silent instead, which is worse for everyone: silence is indistinguishable from ignorance, while "we are looking at it, here is the timestamp" is a signal your customers can plan against.

The Five Not-Affected Justifications, Mapped to Real False Positives

A NOT AFFECTED status requires an impact justification, drawn from a fixed set of five. This is the part that turns an abstract vocabulary into a triage lookup table, so here is each one against the false positive it actually describes:

component_not_present. The vulnerable component is not in the delivered product at all. Typical case: the scanner matched a build-time or test-only dependency that never reaches the runtime image, or a package present in an earlier layer that a multi-stage build discards.

vulnerable_code_not_present. The component is there, but the specific vulnerable code is not. Typical case: a vendored subset, a build flag that excludes the affected module, or a distribution that backported a fix without changing the version string, which is the single most common source of false positives on Linux base images.

vulnerable_code_not_in_execute_path. The vulnerable function exists in the shipped library and is never called. This is the big one, the 41 findings from the opening paragraph, and it is what reachability analysis tools are trying to compute automatically.

vulnerable_code_cannot_be_controlled_by_adversary. The code runs, but no attacker-controlled input reaches it. Typical case: a parser vulnerability where the only input is a configuration file you write at build time.

inline_mitigations_already_exist. A compensating control in the product prevents exploitation. Typical case: the vulnerable endpoint is unreachable because it is bound to localhost, or a WAF rule blocks the trigger, or the feature is disabled by default and you ship it disabled.

Note how specific these are. That specificity is deliberate: it forces the person dismissing a finding to say which kind of not-applicable they mean, and that single act catches a meaningful number of wrong dismissals before they become permanent.

Minimum Required Fields for a Usable VEX Statement

Regardless of format, a statement needs:

  • VEX metadata: an identifier for the document, the author, and a timestamp.
  • Product identifier: precise enough to be unambiguous. A PURL or a CPE, plus a version or version range.
  • Vulnerability identifier: normally a CVE ID.
  • Status, plus a justification if NOT AFFECTED, or an action statement if AFFECTED.

Two of those deserve emphasis. The timestamp is what makes VEX a series of assertions over time rather than a document: a status legitimately changes as an investigation completes or a fix ships, and each statement carries its own moment. And the justification is what makes NOT AFFECTED worth anything at all.

The Trap: NOT AFFECTED With No Justification

A bare NOT AFFECTED is not actionable. It is a vendor asserting "trust me" in machine-readable form.

Producers should not publish it. Consumers should reject it, or at minimum flag it for manual review rather than suppressing the finding. If you take one operational rule from this article, make it that one: treat an unjustified NOT AFFECTED as UNDER INVESTIGATION.

Three Formats: CSAF, CycloneDX VEX and OpenVEX

  • CSAF 2.0 VEX profile (OASIS). The heavyweight standard. Several large vendors publish CSAF advisories, and enterprise consumers are most likely to already parse it. Verbose, and the tooling assumes a formal advisory process.
  • CycloneDX VEX (OWASP). VEX expressed in the same schema family as CycloneDX SBOMs. If you already emit CycloneDX, this is the least friction by a wide margin.
  • OpenVEX (OpenSSF). Deliberately minimal. A small JSON document with the required fields and nothing else, designed to be generated by a CLI and committed to a repository.

Which Format to Pick, and Why It Depends on Who Reads It

The choice is not technical, it is about the consumer:

  • Publishing to enterprise customers with security teams: CSAF.
  • You already produce CycloneDX SBOMs: CycloneDX VEX.
  • The reader is your own scanner and your own auditor: OpenVEX.

That last case is the one most teams are actually in, and it is where this article's main argument lives.

The Internal Use Case: Stop Re-Litigating the Same False Positive

VEX is almost always presented as a supplier artefact you publish for customers. For most engineering teams that is its least useful application.

The highest-value use is internal. Commit VEX statements to the repository, next to the code, and feed them back into your scanner as suppressions with reasons attached.

Compare the two artefacts you could have:

  • A .trivyignore file containing eleven bare CVE IDs, added by four people who have since left.
  • Eleven timestamped statements, each naming an author, a status, and one of five standard justifications.

Both silence the same findings. Only one survives a personnel change, and only one can be reviewed. The second also happens to be exactly the dated exploitability record an auditor asks for, which means the work you were already doing in a Slack thread becomes compliance evidence at no extra cost.

This is also the honest answer to alert fatigue. We have argued that most CVE alerts are noise and that filtering has to happen before the human. VEX is the mechanism for making a human's filtering decision stick, permanently and reviewably, so the same judgement is never made twice.

Consuming VEX Documents From Your Suppliers

Going the other way is harder than it sounds. To consume supplier VEX you need to:

  1. Discover the document. There is no universal registry. Check the vendor's security page, their CSAF distribution endpoint, or their repository.
  2. Match their product identifier to what you actually deploy. This is the step that breaks, because their PURL and your inventory rarely agree on granularity.
  3. Apply the statement to your findings, keeping the justification visible rather than silently suppressing.
  4. Re-check on a schedule, because statements go stale and statuses change.

Step 2 is the reason so many teams give up. Precise product identification across an estate assembled from a dozen vendors is genuinely unsolved, and it is the same matching problem that makes dependency-level supply chain attacks hard to detect. It is also why our stack tracking matches on concept tags alongside vendor identifiers rather than requiring an exact CPE string: an identifier scheme that only works when everyone agrees is an identifier scheme that does not work.

Where VEX Breaks Down

Four honest limits:

  • Product identification. As above. Everything downstream depends on it.
  • Statements go stale. A NOT AFFECTED from 2024 says nothing about the version you shipped last week.
  • No universal registry. Discovery is manual and per-vendor.
  • Uneven consumption tooling. Producing VEX is well supported; automatically consuming third-party VEX and applying it to your findings is still rough in most scanners.

None of these undermine the internal use case, which is precisely why the internal use case is the one to start with.

Regulatory Pressure Is Building

The EU Cyber Resilience Act requires manufacturers to document components and to address vulnerabilities without delay, on a clock. Documenting why a vulnerability in a listed component does not require action is a gap that VEX fills exactly, and doing it in a machine-readable format that predates the audit is a great deal cheaper than reconstructing the reasoning afterwards. Our CRA guide covers those obligations and the 24-hour reporting clock in detail.

A Starting Workflow You Can Run This Week

  1. Pick OpenVEX. It is the smallest thing that works.
  2. Take the findings your team has already dismissed and write one statement each, with a real justification from the five.
  3. Commit them to the repository, in a vex/ directory beside the code they describe.
  4. Wire them into the scanner as suppressions, keeping the justification in the output.
  5. Add one rule to code review: a dismissal without a justification does not merge.

Step 5 is the whole discipline. Everything else is file format.

Frequently asked questions

What is the difference between an SBOM and a VEX document?

An SBOM lists what components are in a product. A VEX statement says whether a specific vulnerability actually affects that product, and if not, why. The SBOM tells you a vulnerable library is present; VEX tells you the vulnerable function is never reachable, so the finding is not actionable.

What are the four VEX statuses?

NOT AFFECTED, AFFECTED, FIXED and UNDER INVESTIGATION. A NOT AFFECTED status must be accompanied by one of five standard impact justifications, otherwise downstream consumers have no basis to trust it.

Which VEX format should I use?

If you already emit CycloneDX SBOMs, CycloneDX VEX is the least friction. If you publish advisories to enterprise customers who consume machine-readable feeds, the CSAF 2.0 VEX profile is the format they are most likely to already parse. If you just want the smallest possible file committed alongside your code, use OpenVEX.

Can we use VEX internally without publishing anything?

Yes, and for most engineering teams that is the higher-value use. Committing VEX statements next to your code records why each finding was dismissed, stops the same false positive being re-investigated every sprint, and gives an auditor a dated, reviewable trail of exploitability decisions.

Stay ahead of threats

Get AI-filtered CVE alerts for your specific tech stack. Free to start.

Start for free