Which justification labels can back a VEX not_affected status, and why is one required?
answer
- five labels, not free text
- ordered from absent to merely neutralised
- one covers code compiled out
- one covers inputs an attacker cannot supply
- the weakest one depends on configuration staying put
basics
~10 sFive labels: 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. One is required because a bare not_affected is an unauditable assertion nobody downstream can re-check.
solid answer
~50 sA `not_affected` statement must say *why*, either with one of the five standard justification labels or with a written impact statement. The labels form a ladder from strongest to most contingent: `component_not_present` (the component is not in the product at all), `vulnerable_code_not_present` (the component is there but the flawed code is not — compiled out, stripped, or in a module you do not ship), `vulnerable_code_not_in_execute_path` (present but never invoked), `vulnerable_code_cannot_be_controlled_by_adversary` (invoked, but nothing an attacker supplies reaches it), and `inline_mitigations_already_exist` (reachable and controllable, but a mitigation already in the product blocks exploitation). The requirement exists because the consumer, not the author, carries the residual risk. A label tells them which of *their* assumptions the claim depends on: the first two survive almost any deployment, the last three are conditional on configuration and can stop being true in someone else's environment.
code
json · 14 lines{
"@context": "https://openvex.dev/ns/v0.2.0",
"author": "Example Corp Product Security",
"timestamp": "2026-03-04T10:00:00Z",
"version": 1,
"statements": [
{
"vulnerability": { "name": "CVE-YYYY-NNNNN" },
"products": [ { "@id": "pkg:pypi/[email protected]" } ],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path"
}
]
}go deeper
Know that not_affected always needs a reason attached and be able to name at least three of the five labels. The distinction to hold: the component being absent is a different claim from the component being present but never called.
Explain each label's precondition and where the justification physically sits in the document. Be ready to pick the right one for a described situation and to say what would make your pick stop being true.
Show judgment about defensibility: why you take the weaker true label over the stronger false one, and how you decide which suppliers' contingent justifications you re-check against your own deployment before honouring them.
Set the standard for what your organisation is allowed to publish — which labels need sign-off, which require re-validation when the product changes, and who is accountable when a contingent justification goes stale.
## Why a status alone is not enough A `not_affected` statement is a supplier telling a consumer to stop worrying about a published vulnerability. The consumer is the one who eats the consequences if that is wrong, and they generally cannot inspect the product's source. So the format makes the reasoning mandatory: a `not_affected` claim must be accompanied by either a machine-readable justification label or a written impact statement setting out the argument. A status with no reason is not auditable, not comparable across suppliers, and — the practical failure — not re-checkable by the consumer when their deployment differs from the one the author had in mind. ## The five justification labels These are the standard labels shared across the VEX ecosystem; the same five appear as OpenVEX justifications and as CSAF flag labels, which is what lets a consumer normalise documents from different suppliers. **`component_not_present`** — the vulnerable component is not in the product at all. The strongest claim available, and the most easily falsified: if you also publish an inventory for the same artifact and it lists the component, you have contradicted yourself in public. **`vulnerable_code_not_present`** — the component ships, but the specific flawed code does not. Typical causes: the vulnerable function is behind a build flag you do not enable, the affected module is stripped from the distributed package, or you vendor only part of the library. This claim is a property of how the artifact was built. **`vulnerable_code_not_in_execute_path`** — the flawed code ships and could run, but nothing in the product ever calls it. This is where most real `not_affected` claims live, and it is the first genuinely *contingent* label: it is true of a configuration and a set of entry points, not of a binary. **`vulnerable_code_cannot_be_controlled_by_adversary`** — the flawed code does execute, but an attacker cannot influence the inputs that would trigger it. Think of a parser that only ever reads a file the operator placed there by hand. The claim depends on your threat model for who supplies the input, so it is only as good as the assumption that nobody hostile ever gets to write that input. **`inline_mitigations_already_exist`** — the code is present, reachable and reachable with attacker-influenced input, but a mitigation built into the product neutralises exploitation. The most contingent of the five: mitigations get disabled, configured away, or bypassed. ## Read the ladder as a risk gradient A useful way to hold these is that they descend from *structural* to *conditional*: | Justification | What it is a property of | Survives being redeployed elsewhere? | | --- | --- | --- | | component_not_present | the artifact's contents | yes | | vulnerable_code_not_present | how the artifact was built | yes | | vulnerable_code_not_in_execute_path | how the product invokes the code | usually, but configuration can break it | | vulnerable_code_cannot_be_controlled_by_adversary | the threat model for inputs | only if the consumer's threat model matches | | inline_mitigations_already_exist | the running configuration | only while the mitigation stays on | The consumer's job on receiving a statement is to decide whether the label's precondition holds in *their* deployment. That is precisely the decision the label makes possible and a bare status makes impossible. ## Choosing the label you can defend The temptation is always to reach up the ladder — `component_not_present` sounds the most reassuring and closes the ticket hardest. Resist it. The strongest-sounding true claim is the right one; a stronger claim that is false is a public, checkable falsehood, and it poisons every other statement you have published because it tells consumers your statements are marketing rather than analysis. If you are uncertain between two labels, the honest move is the weaker one, or the free-text impact statement where you can state the precondition in words the label cannot express. ## Impact statement versus label The written impact statement exists for cases the vocabulary does not cover, or where the argument needs a sentence of context. It is strictly worse for automation — a consumer's tooling can filter on a label but has to route prose to a human — so prefer a label where one honestly fits, and use the impact statement to *add* the precondition rather than to avoid choosing. ## Where the justification sits In OpenVEX it is a `justification` field on the statement itself, alongside the `status`. In the CSAF VEX profile the equivalent information rides as a flag with the same label vocabulary attached to the product status. A CycloneDX BOM can carry inline vulnerability analysis with its own justification vocabulary, which is close in spirit but not identical in wording — another reason consumers normalise everything they ingest to one internal vocabulary before comparing suppliers.
- When would you use a written impact statement instead of one of the five labels?When no label is honestly true and the argument needs a sentence — for example when the precondition is a specific deployment setting the vocabulary cannot express. Prose is worse for automation, because a consumer can filter on a label but has to route free text to a human. So use it to add the precondition, not to dodge picking a label you would rather not defend.
- Which of the five labels are safest for a consumer to accept without re-checking?component_not_present and vulnerable_code_not_present, because both are properties of the artifact itself and hold wherever it runs. The other three are conditional on how the product is invoked, on the threat model for inputs, or on a mitigation staying enabled — all of which the consumer can break in their own deployment without noticing.
- Does issuing not_affected mean the component can stay unpatched forever?No. The justification is a claim about today's build and configuration. A refactor that adds a call into the previously dead code, or a config change that turns off an inline mitigation, silently invalidates the statement. Treat every contingent justification as a standing assumption that has to be re-checked when the product changes, not as a permanent exemption.
saying these in an interview costs you the question
- Invents justification labels beyond the five defined ones
- Uses component_not_present when the component ships but is unused
- Treats a justification as optional decoration on not_affected
- Thinks a free-text impact statement is always the safer choice
- Assumes a justification stays true across product versions