OpenVEX Vulnerability Triage

scan_components (the OSV matcher) produces vulnerability findings, but every known CVE then shows up on every run forever — there was no way to record “we looked, this one does not affect us” and drop it. VEX (Vulnerability Exploitability eXchange) is the standard for exactly that triage signal. This authors OpenVEX 0.2.0 statements and applies them to the scanner’s findings.

not_affected / fixed statements suppress a finding; affected / under_investigation annotate it with the assessed status. Statements join to findings on the vulnerability id or any of its aliases, optionally scoped to a product. Pure standard library (hashlib + json + datetime); imports no PySide6.

Headless API

from je_auto_control import (
    scan_components, vex_statement, build_vex, apply_vex)

findings = scan_components(sbom["components"], advisories)

statements = [
    vex_statement("CVE-2024-1", "not_affected",
                  products=["pkg:pypi/foo"],
                  justification="vulnerable_code_not_present"),
    vex_statement("GHSA-bar", "under_investigation"),
]
vex = build_vex(statements, author="security@example.com")

triaged = apply_vex(findings, vex)
# CVE-2024-1 (not_affected) dropped; GHSA-bar kept with vex_status set

vex_statement validates the inputs: status must be one of VEX_STATUSES (not_affected / affected / fixed / under_investigation); a not_affected statement must carry a justification (one of VEX_JUSTIFICATIONS) or an impact_statement. build_vex wraps statements in an OpenVEX document (pass an explicit timestamp for a reproducible @id). apply_vex returns the surviving findings, each non-suppressed match annotated with vex_status.

Executor command

AC_apply_vex takes findings and a vex document (each a list/object or a JSON string) and returns {findings, count} of the survivors. The same operation is exposed as the MCP tool ac_apply_vex and as a Script Builder command under Security. It chains directly after AC_scan_vulns.