Skip to content

feat: write an OpenVEX document justified by reachability - #126

Merged
SizzleUnrlsd merged 2 commits into
mainfrom
feat/openvex
Sep 24, 2026
Merged

SizzleUnrlsd merged 2 commits into
mainfrom
feat/openvex

Conversation

@SizzleUnrlsd

Copy link
Copy Markdown
Contributor

Closes #97.

--vex PATH writes an OpenVEX 0.2.0 document on a directory check, next to --sbom. It has one statement per advisory affecting a requirement, with the required package as a subcomponent of the project.

Statuses

Status When
affected The project's code reaches the vulnerability, at level reachable or exploitable. This includes findings the policy accepts or an inline comment suppresses, since accepting a risk does not make the project unaffected. status_notes lists every place with its highest level. action_statement names the package and its vulnerable range.
not_affected Justified as vulnerable_code_not_in_execute_path, only when all four conditions hold: (1) the advisory names entry points; (2) nothing reaches them; (3) every file and function was analysed; (4) a lock file shows that no other package requires the vulnerable one. The impact_statement names the entry points, plus any calls ruled out by their arguments.
under_investigation Anything else. status_notes says which condition failed: no entry point, files not fully analysed (named), no lock file, or the packages that require the vulnerable one.

This differs from the issue text in two places, both on purpose:

  • Lock-file condition. The issue asked for not_affected whenever there is no reachable-vulnerability finding. But the engine reads the project's code, not the code of installed packages. A framework calling the vulnerable function on the project's behalf would be missed, and the claim would be false. On luigi, httplib2 (CVE-2026-59939) is never called by luigi's own code, but google-api-python-client and google-auth-httplib2 require it. It now comes out under_investigation, naming both.
  • --vex PATH, not a fourth --format. VEX describes dependencies, like the SBOM, not the findings of a report. A separate file lets one run produce SARIF, an SBOM and VEX together.

Supporting changes

  • Lock-file edges. uv.lock and poetry.lock now record which packages require which: DependencyGraph.required_by(name) gives the other packages requiring a package. Optional extras and development groups count. The project's own packages (uv's editable or virtual sources inside the tree, such as luigi's editable = ".") do not. An editable package outside the tree does count. It returns None when no lock file mentions the package.
  • Accepted findings. ProjectAnalysis.accepted keeps the findings of advisories the policy accepts. Policy.accepts(finding) is the single predicate behind it and behind apply_policy.
  • Document fields. The document @id sits in OpenVEX's shared namespace and derives from the statements, not the timestamp, so two runs with the same result share it. The author is Unknown Author, as govulncheck writes when it cannot know it. The product must be an IRI, so it is pkg:generic/<directory>.

Verification

  • pytest: 991 passed.
  • pytest -m regression under Python 3.12: 19 passed, snapshots unchanged.
  • ruff check src tests and mypy are clean.
  • Every status rule was mutation-checked. Each of these changes fails a test: dropping the accepted evidence, the coverage condition, the lock-file condition or the dependents condition; treating an advisory without entry points as curated; keeping the last level instead of the highest; hashing the timestamp into @id. The lock-file parsing was mutation-checked the same way: extras, merge union, the project's own packages, an editable package outside the tree.
  • Real runs with the curated enterprise feed:
    • pygoat: 9 affected, 12 under investigation. It has no lock file, and two files have syntax errors.
    • luigi: 2 affected, 1 under investigation (httplib2, above).

Not done

  • Product ID. A --vex-product option, for a pipeline that must match a container image or a published package, can come when a scanner needs it.

@SizzleUnrlsd SizzleUnrlsd self-assigned this Sep 24, 2026
@SizzleUnrlsd
SizzleUnrlsd merged commit 72697fe into main Sep 24, 2026
10 checks passed
@SizzleUnrlsd
SizzleUnrlsd deleted the feat/openvex branch September 24, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reporters: VEX output justified by reachability

1 participant