Safeguard
Supply Chain Security•

VEX in practice: one document, four statuses, and when to update it

A working routine for small teams that answer customer vulnerability questions with OpenVEX, using the v0.2.0 spec and vexctl v0.4.4 from June 2026.

Safeguard Security Team
5 min read

When a headline vulnerability lands, every customer with your SBOM asks the same question: are we affected? A VEX document is the written answer. vexctl v0.4.4, the OpenVEX command line tool, was released on 16 June 2026, and the OpenVEX specification it implements is at v0.2.0. This post walks through how a small team can run VEX as a routine rather than a one-off reply.

CISA's 2025 draft SBOM minimum elements describes the payoff: VEX and CSAF advisories "help avoid unnecessary effort if a trusted source deems that potential risk does not translate into actual risk."

What's affected

Any vendor whose customers scan its SBOM and then email a list of CVE matches. Without VEX, you answer each match by hand, every time.

OpenVEX gives each statement one of four statuses:

StatusMeaning in the spec
not_affectedNo remediation is required. A justification is required.
affectedActions are recommended. An action statement SHOULD describe them.
fixedThese product versions contain a fix.
under_investigationIt is not yet known. Updates should follow in further documents.

For not_affected, the spec requires either a machine readable justification label or a free form impact statement, and discourages relying on free text alone. The five labels are component_not_present, vulnerable_code_not_present, vulnerable_code_not_in_execute_path, vulnerable_code_cannot_be_controlled_by_adversary and inline_mitigations_already_exist. The spec itself warns that the last two "could be difficult to prove conclusively."

How to check if you're exposed

Take the worked example from the OpenVEX spec. Spring Boot 2.6.0 shipped a Log4j library inside the range affected by CVE-2021-44228, but the maintainers explained that it was not exploitable as shipped. Only applications that switched to Log4j2 and used log4j-core were at risk. The spec captures that as one statement: status not_affected, justification vulnerable_code_not_in_execute_path, plus an impact statement. The component in question is log4j-core.

That is the test for your own claims. Can you point to the fact that makes the statement true, such as a build flag, a call graph or a removed module? If you can, write not_affected. If you cannot, write under_investigation.

What to do now

  1. Create the statement when the question arrives, even if the status is only under_investigation:

    vexctl create --product="pkg:npm/your-app@2.4.0" \
                  --vuln="CVE-2021-44228" \
                  --status="under_investigation"
    
  2. When you know, issue a new document, not a silent edit. The spec says the document version "must be incremented when any content within the VEX document changes," and a statement keeps its own timestamp when you add another one beside it. The spec's own example shows under_investigation followed by fixed in version 2 of the same document.

  3. For not_affected, pick the justification label that matches your evidence. Reachability analysis, meaning whether your code calls the vulnerable function, is the evidence behind vulnerable_code_not_in_execute_path. Keep the call path in your ticket.

  4. For affected, add an action statement: upgrade target, mitigation, or date you expect a fix.

  5. Merge statements from several sources when you need one picture:

    vexctl merge --product=pkg:npm/your-app@2.4.0 vendor.vex.json ours.vex.json
    
  6. Apply the result to scanner output so the noise drops:

    vexctl filter scan_results.sarif.json ours.vex.json
    
  7. Publish VEX next to each SBOM at a version specific URL, and name an owner for updates.

How to avoid this trap

  • Do not use not_affected because a fix is "planned." That is affected with an action statement.
  • Do not overwrite an old statement. Add a new one so customers can see the history.
  • Do not rely on inline_mitigations_already_exist unless the protection cannot be disabled by a user or bypassed by minor variations.

Why it matters

A VEX routine turns repeated questions into one maintained record. It also forces an honest internal habit: every "not affected" has a reason someone can read. That reason is what a careful customer actually wants.

Try it yourself

Pick one open CVE from your last scan and write the statement. Choose a status, choose a label, and write the one sentence of evidence. If you cannot write the sentence, the status is under_investigation.

Click through it at your own pace, or open it full screen.

Quick check

What must an OpenVEX statement with status not_affected include?

Either a justification label or an impact statement. The spec discourages impact statements alone for automated use.

You learn a CVE does affect a version you called under_investigation. What do you issue?

A new version of the document with an updated statement. The document version must increase whenever content changes.

Why is vulnerable_code_not_in_execute_path a better label than a free text claim?

It is a fixed label that tools can read, and it ties the claim to evidence such as reachability analysis.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.