TL;DR: VEX attaches to a component identity, and every SBOM tool spells that identity differently - one writes "python-dateutil," another "dateutil," versions get normalized differently. Canonicalize every component to a package URL, pin a single SBOM generator, and re-attach VEX by purl so a tool swap cannot rewrite your verdicts.

```text
VEX status flipped from "not_affected" to "affected" after the agent regenerated the SBOM with a different tool
```

## Steps

1. Take the old SBOM and the new SBOM and put the same component side by side: compare the name, version, and purl fields.
   Expected: the names or version strings differ even though it is the same installed package.
2. Rewrite your VEX matching key from the tool's name field to the package URL (purl) - for example, the purl for python-dateutil pinned to version 2.8.2 - and backfill the existing VEX statements with purls.
   Expected: each VEX statement points at an identity both tools can produce.
3. Pin one SBOM generator and version for the pipeline, and record it in the SBOM metadata.
   Expected: no more silent generator swaps between runs.
4. If you must switch tools, run old and new side by side for one cycle and diff the component lists by purl before cutting over.
   Expected: a short list of naming mismatches to fix, instead of a flipped VEX history.
5. Re-issue the flipped VEX statements against the canonical purls and verify the statuses return to "not_affected" with their original justifications.
   Expected: the verdict history is stable again, tied to identity rather than to whichever tool ran last.

## Use this when

- VEX statuses change after switching SBOM generators
- Two tools (syft, cdxgen, trivy) disagree on a package name or version for the same installed component
- Your VEX statements are keyed on names and a rename orphaned them
- An audit diff shows exploitability verdicts moving with no code change

## Not for this skill when

- The component actually changed (version bump, replaced package) - re-verify reachability on the new component
- The flip came from an advisory update (new exploit published) - that is a real status change, not an identity problem
- You have no SBOM at all and VEX is hand-written - standardize on purls first, then automate

## Variant phrasings

- VEX not_affected became affected after SBOM regeneration
- syft vs cdxgen package name mismatch broke VEX matching
- how to key VEX statements so SBOM tool changes don't flip them

## Why it happens

VEX is a claim about a component, and "the component" has no single spelling - each SBOM tool normalizes names, versions, and qualifiers its own way. When the VEX matcher keys on the tool's name string, a generator swap silently re-keys every component, the old statements stop matching, and findings fall back to the default "affected." The verdicts did not change because the risk changed; they changed because the labels moved.

## Edge cases

- Some components have no clean purl (vendored code, renamed forks) - assign a stable internal identifier and document the mapping.
- Version normalization still bites: "2.8.2" vs "2.8.2-1" are different purls - decide whether your matcher treats build suffixes as the same component.
- Transitive dependencies renamed by one tool but not the other are the most common flip source - diff those first.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_bXgvCUCw9f_NUgKlo5bdcA
