Guide · Vulnerability management

Vulnerability waivers, risk acceptance & VEX

Run software composition analysis long enough and you’ll hit a finding you can’t fix today — no upstream patch, or it isn’t exploitable in your context. Ignoring it is negligent; blanket-muting it is worse. Here’s the responsible middle: risk acceptance, waivers, and VEX.

Updated 29 Jul 2026 · by the depproof team

Your real options for an unfixable finding

When a scan flags a dependency CVE, you have four moves — in order of preference:

  • Upgrade to a fixed version — the real fix, when one exists.
  • Remove or replace the dependency — if you don’t truly need it.
  • Mitigate — configuration or code that neutralizes the path.
  • Accept the risk, deliberately — when none of the above is possible yet. This is where waivers and VEX come in.

The failure mode isn’t choosing to accept a risk — it’s accepting it silently, with no justification, owner, or expiry, so nobody ever revisits it.

Waivers: risk acceptance you can defend

A waiver is a documented decision to accept a specific finding for now, and to stop it from failing your build. A defensible waiver has four things:

  • A justification — why it’s acceptable (no fix yet, not reachable, false positive, compensating control).
  • An owner — who accepted the risk (ideally security, not whoever was unblocking a build).
  • An expiry — a date it’s re-reviewed, so “temporary” doesn’t become forever.
  • A scope — which finding, which repo/product — and the raw finding stays in your inventory. You suppress the gate, not the record.

What is VEX (Vulnerability Exploitability eXchange)?

VEX stands for Vulnerability Exploitability eXchange. It is a machine-readable statement, published by whoever knows the product best — usually its vendor or maintainer — saying whether a specific vulnerability actually affects that product. Not whether the vulnerable component is present: whether it matters.

That distinction is the entire point. A scanner can tell you a CVE exists somewhere in your dependency tree. Only the people who built the product know whether the vulnerable function is ever called, whether an attacker could reach it, or whether a configuration already neutralises it. VEX is the format for saying so in a way tools can consume, instead of a PDF advisory or an email thread.

It answers a different question than your SBOM. An SBOM says what components you ship; VEX says which of their vulnerabilities actually affect you. A VEX statement carries one of four statuses:

VEX statusMeaning
not_affectedThe CVE is present in a component but doesn’t affect your product — with a justification (e.g. the vulnerable code isn’t present, isn’t reachable, or can’t be triggered by an attacker).
affectedYour product is affected; action or a mitigation is recommended.
fixedA release has already remediated it.
under_investigationYou’re still determining whether it affects you.

VEX exists to cut false-positive noise: instead of a customer seeing a scary CVE in your SBOM and filing a ticket, you hand them a not_affected statement that explains why.

“Not affected” needs a reason — and the reasons are fixed

This is the part most summaries skip, and it is what makes VEX more than an opinion. You cannot simply declare not_affected; the specification requires a justification, chosen from a defined set. The OpenVEX specification permits five:

JustificationWhat you are asserting
component_not_presentThe vulnerable component isn’t in the product at all.
vulnerable_code_not_presentThe component is there, but the specific vulnerable code isn’t included.
vulnerable_code_not_in_execute_pathThe vulnerable code ships but is never executed.
vulnerable_code_cannot_be_controlled_by_adversaryIt executes, but an attacker can’t influence it.
inline_mitigations_already_existSomething already in the product blocks exploitation.

Being made to pick one is a discipline, not a formality. “We looked and it’s fine” is not expressible in VEX — you have to commit to which kind of fine, and that claim is auditable later. It is the same rigour a good waiver justification demands, in a form you can hand to someone outside your company.

The three VEX formats

VEX is a concept with several encodings. They express the same core idea and differ mainly in weight and in what else they travel with:

  • OpenVEX — a minimal, standalone JSON format. The easiest to generate and consume, and the usual starting point.
  • CycloneDX VEX — embedded in, or shipped alongside, a CycloneDX SBOM. Natural if you already produce CycloneDX; note it defines its own status and justification labels rather than reusing OpenVEX’s.
  • CSAF VEX — a profile of the OASIS CSAF standard. The heaviest, and the one large vendors and regulators tend to expect.

Pick by audience, not by preference: emit what the party asking for it can actually ingest.

Waiver vs VEX — related, not the same

A waiver is your internal, operational decision: don’t fail our build on this, here’s why, expires then. VEX is a formal, shareable statement about exploitability, often for downstream consumers. They reinforce each other — a “not affected” determination can both suppress your own gate and generate the VEX you publish. The best workflow makes one flow into the other.

None of this works without a trustworthy finding underneath

A VEX statement is a claim about a specific component at a specific version. That means its quality is capped by the scan that produced the finding — you cannot honestly declare vulnerable_code_not_present for a dependency you didn’t know you shipped.

Two properties of the underlying scan decide whether your VEX is worth anything:

  • The full transitive tree, not just direct dependencies. Most vulnerable components arrive several levels down, through packages you never chose. A VEX built on a partial tree is confidently wrong about the part it never saw — and an incomplete scan looks identical to a clean one.
  • Resolved input, not declared intent. A lockfile or a resolved dependency graph states what you actually ship; a manifest states what someone asked for. Version-specific claims need the former.

There is a confidentiality angle too, and it matters for exactly the audience that asks for VEX. Exploitability analysis means reasoning about your own source and configuration — which is a strong argument for producing this evidence with a self-hosted scan that runs inside your infrastructure, rather than shipping the code to a vendor cloud to be told what is in it. The Maven and Gradle and npm guides show what that scan looks like in practice.

Doing it well

  • Per-finding, not blanket — accept individual findings with reasons, never “ignore all mediums.”
  • Security-owned — risk acceptance is a security decision, not a build-unblocking shortcut.
  • Time-bound — expiries force re-review, catching the “not reachable” that became reachable.
  • Audited — an append-only trail of who waived what, when, and why: the evidence an audit needs.
  • Exportable — produce VEX (OpenVEX/CycloneDX/CSAF) so downstream teams and customers get the answer, not just the noise.

How depproof handles it

The depproof hub is built exactly around this: waive a finding from the repo page with a justification and expiry, keep an append-only audit trail with posture snapshots, and export OpenVEX or CSV. And because the hub enforces waivers centrally, a finding your security team has accepted stops failing every repo’s CI gate — fail-closed, so an unreachable hub never silently turns a red build green. See the hub release for the details.

Frequently asked questions

What is a vulnerability waiver?

A vulnerability waiver is a documented decision to accept a known finding for now instead of fixing it — because there is no upstream fix, it isn’t exploitable in your context, or the risk is acceptable. Done well, a waiver records who accepted it, why, its scope, and an expiry date, and it suppresses that finding from your CI gate so the build stops failing — without deleting the finding from your inventory. It’s risk acceptance with an owner and a clock, not a permanent mute.

What is VEX?

VEX (Vulnerability Exploitability eXchange) is a machine-readable statement about whether a specific vulnerability actually affects a product. Rather than “this CVE is present,” VEX answers “are we affected?” with a status — not_affected, affected, fixed, or under_investigation — and, for not_affected, a justification (for example, the vulnerable code isn’t present, isn’t reachable, or can’t be triggered by an attacker). It exists to cut through false-positive noise: an SBOM says what components you ship; VEX says which of their vulnerabilities matter.

What does VEX stand for?

VEX stands for Vulnerability Exploitability eXchange. It is a machine-readable statement, published by the party that knows the product best — usually the vendor or maintainer — declaring whether a specific vulnerability actually affects that product, rather than merely whether the vulnerable component is present. The point is to communicate exploitability in a form tools can consume, instead of a PDF advisory or an email thread.

What is the difference between VEX and an SBOM?

They answer different questions and are designed to be used together. An SBOM is an inventory: it lists the components a product contains. VEX is an assessment: for a given vulnerability in one of those components, it states whether the product is actually affected, and if not, why. An SBOM alone tends to generate false-positive alarm — a customer sees a CVE against a listed component and raises a ticket. VEX is what lets the vendor answer "we ship that component, and this CVE does not affect us, for this specific reason."

Who is supposed to produce a VEX document?

Whoever has the knowledge to make the exploitability determination — in practice the software producer: the vendor, the maintainer, or an internal team for software their own organisation ships. It is not something a consumer can generate about someone else’s product, because the judgement depends on knowing whether the vulnerable code is reachable in that product. Consumers receive VEX; producers publish it.

Can you declare a vulnerability "not affected" without giving a reason?

No. Under the OpenVEX specification a not_affected status requires a justification drawn from a defined set: component_not_present, vulnerable_code_not_present, vulnerable_code_not_in_execute_path, vulnerable_code_cannot_be_controlled_by_adversary, or inline_mitigations_already_exist. That constraint is what gives VEX its value — a vague "we checked and it is fine" cannot be expressed, so every claim commits to a specific, auditable kind of not-affected.

What is the difference between a waiver and VEX?

A waiver is your internal operational decision — “don’t fail our build on this finding, here’s why, expires on this date.” VEX is a formal, shareable statement — often given to downstream consumers or customers — about whether your product is exploitable for a given vulnerability. They’re complementary: a “not affected” determination can drive both a waiver (suppress it in your own gate) and a VEX document (communicate it outward). A good tool lets you produce VEX from the waiver decisions you’ve already made.

What is OpenVEX?

OpenVEX is a minimal, open VEX format from the OpenSSF — a lightweight JSON way to express the same not_affected / affected / fixed / under_investigation statements and justifications. Other VEX formats include CycloneDX VEX (embedded in or alongside a CycloneDX SBOM) and CSAF VEX (the OASIS standard). They carry the same core idea; OpenVEX is the easiest to generate and consume.

Is it safe to suppress a vulnerability finding?

It’s safe when the suppression is deliberate, justified, time-bound, and audited — and dangerous when it’s a blanket mute nobody revisits. The risk of blanket suppression is that a finding you accepted as “not reachable” becomes reachable after a refactor and no longer alerts. That’s why good practice is: accept per-finding with a written justification, set an expiry so it’s re-reviewed, keep an audit trail, and keep the raw finding in your inventory (suppress the gate, not the record).

Sources

Specification details last checked 16 August 2026. Formats evolve — verify against the primary sources above before building against them.

General information on handling dependency risk — pairs with scanning your dependencies for the findings in the first place.