Working with VEX in DevGuard

Every scan produces findings, and a large share of them do not actually affect your product. VEX (Vulnerability Exploitability eXchange) is the standard for saying so in a way that machines — your own dashboard, your auditors' tooling, your customers' scanners — can read. This section covers everything you do with VEX in DevGuard: recording a decision, automating it, importing decisions from suppliers, and publishing your own.

If you first want to understand the standard itself rather than the workflow, read What is a VEX Report.

Pick the right tool

DevGuard offers two ways to state something about a vulnerability, and the difference is reach.

A vulnerability eventAccept or False Positive — assesses one specific finding, once. It is the right choice when the reasoning genuinely applies to this finding alone. See Create Vulnerability Events.

A VEX rule pairs a CEL expression with the same decision and applies it to every finding that matches — those already in the repository and any that later scans turn up. See Create & Manage VEX Rules.

The typical workflow

  1. Triage a finding. Open a dependency vulnerability and look at the Path to component graph — it shows exactly how the vulnerable package enters your product.
  2. Decide whether it is exploitable. Is the vulnerable code shipped, reached, and reachable by an attacker?
  3. Record the decision. Click the graph edge that is not actually traversed; DevGuard pre-fills a VEX rule for you. Or write your own expression in the playground — see the CEL Expression Reference.
  4. Pick a justification. A false positive requires one of five standardized reasons — see Choose a mechanical justification below.
  5. Publish it. Your decisions become machine-readable CycloneDX VEX, OpenVEX and CSAF documents. See Export & Publish VEX.

Symmetrically, your suppliers publish their own VEX documents — DevGuard consumes them and turns each statement into a rule for your asset. See Import VEX from Files & Suppliers.

Beyond suppliers, DevGuard also pools rules across DevGuard organizations: if enough independent organizations have already written the same rule for a vulnerability you're triaging, it appears as a crowdsourced recommendation you can adopt with one click, weighted by an organization trust score so a single account can't manufacture consensus. See Create & Manage VEX Rules.

Where to find VEX in the web UI

WhatWhere
VEX rules, playground, upstream sources, recommendationsDependency Risks → VEX Rules (asset level)
Assessing a single finding, path graph, rule pre-fillDependency Risks → <a vulnerability>
Public SBOM / VEX / CSAF URLs, Paranoid ModeRepository Settings

Choose a Mechanical Justification

When you mark a vulnerability as a false positive, DevGuard requires a mechanical justification — a machine-readable reason from a fixed vocabulary of five values. Unlike your free-text justification, this value is what other organizations' tooling actually reads. Picking the right one is the difference between a claim that can be audited and one that merely looks tidy.

The vocabulary is defined by the VEX standard, not by DevGuard, and is identical across CSAF and OpenVEX.

The five justifications

They are ordered here as they appear in the DevGuard dropdown.

Component Not Present

The vulnerable component is not part of the product.

The component appears in the SBOM but not in what you actually ship. Typical causes: a build-time-only or test-only dependency, a package that a multi-stage container build discards, or a stale SBOM.

Use this when you can show the artifact does not contain the component at all.

Vulnerable Code Not Present

The component exists, but the vulnerable code was excluded.

The library ships, but the specific vulnerable function does not — because of a build flag, a compile-time feature toggle, tree-shaking, or a distribution's backported patch that fixes the flaw without changing the upstream version number.

Backports are the most common reason for this justification in practice: Debian's openssl 1.1.1n-0+deb11u4 carries the fix while still reporting a version a scanner reads as vulnerable. See Reduce False Positives.

Vulnerable Code Not In Execute Path

The vulnerable code exists but is never executed.

The vulnerable function is present in the binary but nothing in your product ever calls it. This is DevGuard's default selection, and the one the dependency path graph generates when you cut an edge — it is exactly the claim "this edge does not call the vulnerable function".

It is also the justification you can prove most rigorously: symbol-level binary analysis shows whether a call exists at all. See Prove a Finding Is Not Exploitable.

Vulnerable Code Cannot Be Controlled By Adversary

The attacker cannot control the vulnerable code.

The vulnerable code does run, but no attacker-controlled input reaches it. For example, a parser flaw triggered only by a configuration file you write yourself, or a function called exclusively with constants.

This is the hardest justification to defend, because it rests on an argument about your whole trust boundary rather than on a single observable fact. State the boundary explicitly in your free-text justification, and revisit the decision whenever the input surface changes.

Inline Mitigations Already Exist

Built-in defenses prevent known exploitation paths.

The vulnerable code is reachable with attacker-controlled input, but a compensating control neutralizes the exploit: an input filter, a WAF rule, a seccomp profile, a hardened compiler flag, a network policy that blocks the required access.

Name the specific control and where it is configured. A mitigation nobody can locate is not auditable — and if it is later removed, the justification silently becomes false.

Choosing quickly

QuestionIf yes
Is the component actually shipped?No → Component Not Present
Is the vulnerable function in the shipped binary?No → Vulnerable Code Not Present
Does anything in the product call it?No → Vulnerable Code Not In Execute Path
Can an attacker influence its input?No → Vulnerable Code Cannot Be Controlled By Adversary
Is there a control that blocks exploitation?Yes → Inline Mitigations Already Exist
None of the aboveNot a false positive — remediate, or accept the risk

Work down the list in order: the earlier justifications are stronger claims and easier to verify.

How the values are exported

DevGuard shows you a readable label and exports the standardized value behind it. The two are mechanically related: lowercase the label and join the words with underscores, so Vulnerable Code Not In Execute Path becomes vulnerable_code_not_in_execute_path.

Where that value ends up depends on the format:

FormatStatus for a false positiveMechanical justificationYour free text
CSAFproduct_status.known_not_affectedA flags entry with the value as its label, stamped with the decision dateThe remediation details
OpenVEXstatus: not_affectedThe statement's justification fieldimpact_statement
CycloneDX VEXanalysis.state: false_positivenot carriedanalysis.detail

So CSAF and OpenVEX both convey a claim a recipient's tooling can act on automatically. CycloneDX VEX conveys only that you consider the finding a false positive, plus whatever you wrote — the reasoning is there for a human, not for a machine. If the standardized justification matters to your recipients, hand them CSAF or OpenVEX.

Importing works less symmetrically: a CSAF known_not_affected product does become a false-positive rule, but the flags label is not read back. Rules created from a supplier's document therefore carry only that supplier's free-text justification, with no mechanical justification of their own.

Have feedback? We want to hear from you!

Fields marked with * are required