Guided VEX authoring for OSS maintainers — from zero to a published VEX document in one command.
If you maintain an open source package, you already know the problem:
Your users run
trivy imageorgrypeand get a wall of CVE alerts — many in libraries your package bundles as transitive dependencies, in code paths your package never actually calls. They open issues on your repo asking why you haven't patched it. You know it's not exploitable in your context, but you have no formal way to say so.
VEX (Vulnerability Exploitability eXchange) is the standard that lets you do exactly that. A VEX document is a machine-readable statement like "CVE-2021-45046 does not affect this package because the vulnerable JNDI code path is never reached through our public API." Once published, Trivy and Grype automatically suppress those alerts for every downstream user of your package.
Tools like vexctl and lib4vex are format generators — they take your not_affected decision and write it as OpenVEX/CSAF JSON. That's useful, but it skips the hard part: how do you know if a CVE is exploitable in your code? Security analysis is not most maintainers' day job.
The OASIS December 2025 industry report confirmed this: most maintainers give up at step 1 or step 5 of a 10-step manual VEX authoring process. Not because they don't care — because the tooling assumes expert security knowledge they don't have.
VEX Studio is the "TurboTax for VEX". TurboTax doesn't expect you to know tax law — it asks you questions and derives the right answer. VEX Studio does the same for exploitability:
- CWE-guided questionnaires — For each CVE, the tool looks up its CWE type and asks 2–4 targeted questions. CWE-502 (Deserialization)? "Does your package call the affected deserialization functions with untrusted data?" CWE-79 (XSS)? "Does your package render user-supplied input from this library in a browser?"
- Structured determination — Your Yes/No answers map to the correct VEX status and justification code, with no ambiguity.
- Named, auditable output — The VEX document is named after the package (e.g.
express-4.18.2.openvex.json), timestamped, and includes the full decision audit trail.
The EU CRA's vulnerability reporting requirements take effect September 11, 2026. Every software company selling into Europe must formally track and communicate vulnerability exploitability status. VEX is the mechanism. VEX Studio is the fastest path to compliance for OSS maintainers.
- Python 3.11 or higher — python.org/downloads
git clone https://github.com/secpod/vex-studio.git
cd secpod-vex-studio
python -m venv .venv
source .venv/bin/activate # macOS / Linux
.venv\Scripts\activate # Windows
pip install -e .# Assess a package by PURL — generates SBOM, scans CVEs, guides triage, publishes VEX
vex-studio assess -p pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1
# Python package
vex-studio assess -p pkg:pypi/django@3.2.0
# Node.js package
vex-studio assess -p pkg:npm/express@4.18.2
# Your current Python project
vex-studio assess requirements.txtThe tool walks you through triage for each CVE found and writes a VEX document named after the package — e.g. log4j-core-2.14.1.openvex.json — ready for submission to Aqua VEX Hub.
The assess command runs a guided 4-step workflow:
Step 1: Generate SBOM Builds a CycloneDX SBOM from your package PURL or project files
|
Step 2: Scan CVEs Queries OSV.dev + GitHub Security Advisories for all known CVEs
|
Step 3: Triage For each CVE, asks CWE-specific exploitability questions:
| CWE-502? "Does your package call the affected deserialization functions?"
| CWE-79? "Does your package render user input from this library in a browser?"
| ...guided to a status: not_affected | affected | fixed | under_investigation
|
Step 4: Publish VEX Writes a valid OpenVEX 0.2.0 document named after the package
Each decision is saved as it's made — Ctrl+C never loses progress. Resume with vex-studio triage.
CVE-2021-45046 [log4j-core 2.14.1] CWE-502 Deserialization of Untrusted Data
CVSS: 9.0 CRITICAL
Q1: Does your package call the affected deserialization functions from this library?
[y/n/s] > n
=> Status: not_affected
Justification: vulnerable_code_not_in_execute_path
{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "urn:uuid:49fdfaa8-4c9c-433c-a627-aa88deb11b87",
"author": "SecPod Technologies",
"statements": [
{
"vulnerability": { "name": "CVE-2021-45046", "@id": "https://nvd.nist.gov/vuln/detail/CVE-2021-45046" },
"products": [{ "@id": "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1" }],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path",
"impact_statement": "CVE-2021-45046 does not affect log4j-core@2.14.1 because the vulnerable code path is not reachable through this package's public API."
}
]
}| Command | What it does |
|---|---|
vex-studio assess [target] |
Primary workflow — SBOM + scan + triage + publish in one guided process |
vex-studio generate-sbom [target] |
Generate a CycloneDX SBOM from a Python env, requirements file, or PURL list |
vex-studio scan <sbom> |
Scan an SBOM for CVEs via OSV.dev and GHSA |
vex-studio triage |
CWE-guided interactive triage for each CVE (uses latest scan) |
vex-studio publish |
Generate VEX documents from a saved triage session |
vex-studio status |
Show triage progress for the current session |
vex-studio init |
Save author name and package PURL to .vex-studio.toml |
vex-studio clean |
Remove scan results, sessions, VEX outputs, and/or logs |
vex-studio assess [OPTIONS] [TARGET]
TARGET Project directory, requirements.txt, or '.' (default: .)
-p, --purl Build SBOM from one or more PURLs (repeatable)
--sbom PATH Skip SBOM generation and use an existing SBOM file
--severity Minimum CVE severity: critical | high | medium | low | all
--author TEXT Author name for the VEX document
--product-purl Override product PURL in the output
--no-publish Stop after triage; do not generate VEX output
--vexhub Write VEX in Aqua VEX Hub directory layout
--save-sbom PATH Save the generated SBOM to a specific path
--no-ghsa Skip GitHub Security Advisories enrichment| Input | Example |
|---|---|
| Python requirements file | vex-studio generate-sbom requirements.txt |
| Python project directory | vex-studio generate-sbom . |
| Current Python venv | vex-studio generate-sbom |
| PURL — Python | vex-studio generate-sbom -p pkg:pypi/django@3.2.0 |
| PURL — Maven | vex-studio generate-sbom -p pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1 |
| PURL — npm | vex-studio generate-sbom -p pkg:npm/express@4.18.2 |
| Multiple PURLs | vex-studio generate-sbom -p pkg:pypi/flask@2.0.0 -p pkg:npm/lodash@4.17.21 |
| Any language (via syft) | vex-studio generate-sbom . --method syft |
For non-Python projects (npm, Maven, Go, etc.) via project directory, Syft must be installed.
# 1. Generate SBOM
vex-studio generate-sbom requirements.txt
# 2. Scan for CVEs
vex-studio scan sbom.cdx.json
# 3. Triage interactively
vex-studio triage
# 4. Publish VEX
vex-studio publish
# 5. Publish to Aqua VEX Hub layout
vex-studio publish --vexhub| Input | Output | |
|---|---|---|
| CycloneDX JSON SBOM | ✅ | — |
| SPDX JSON SBOM | ✅ | — |
| OpenVEX 0.2.0 | — | ✅ Phase 1 |
| CSAF 2.0 | — | Planned Phase 2 |
| CycloneDX VEX | — | Planned Phase 2 |
CVE data sources (all free, no API keys required):
| Source | Used for |
|---|---|
| OSV.dev | CVEs for PyPI, npm, Maven, Go, crates.io, NuGet, and more |
| GitHub Security Advisories | GHSA enrichment — additional CVEs and severity data |
| NVD | CWE mappings (Phase 2) |
| CISA KEV | Known exploited vulnerabilities (Phase 2) |
| Status | Meaning |
|---|---|
not_affected |
The CVE does not affect this package (with justification code) |
affected |
The CVE is exploitable; remediation action recommended |
fixed |
The fix has been applied in this version |
under_investigation |
Still being assessed |
| Code | Meaning |
|---|---|
component_not_present |
The vulnerable component is not in the build |
vulnerable_code_not_present |
The vulnerable code does not exist in this version |
vulnerable_code_not_in_execute_path |
The vulnerable code cannot be reached through the public API |
inline_mitigations_already_exist |
A mitigation in the package prevents exploitation |
Existing tools solve format generation — you tell them the status and they format a VEX document. VEX Studio solves exploitability determination — it helps you figure out what the status should be and then emits the same format.
PROBLEM 1: FORMAT GENERATION <- vexctl, lib4vex, vexy solve this
PROBLEM 2: EXPLOITABILITY DETERMINATION <- VEX Studio solves this
| Capability | vexctl | lib4vex | vexy | VEX Studio |
|---|---|---|---|---|
| Discover CVEs automatically | - | - | partial | ✅ |
| Parse SBOM (CycloneDX + SPDX JSON) | - | - | partial | ✅ |
| Generate SBOM from env / PURL / requirements | - | - | - | ✅ |
| CWE-guided exploitability questionnaire | - | - | - | ✅ |
| Auto-suggest upgrade path (semver-aware) | - | - | - | ✅ |
| Audit trail of questions and answers | - | - | - | ✅ |
| Resume interrupted triage sessions | - | - | - | ✅ |
| Output: OpenVEX | ✅ | ✅ | - | ✅ |
| Output: CSAF / CycloneDX VEX | - | ✅ | ✅ | Phase 2 |
| VEX Hub-compatible publish layout | - | - | - | ✅ |
| Cosign / sigstore signing | ✅ | - | - | Phase 2 |
# VEX Studio: discover, decide, write VEX
vex-studio assess -p pkg:oci/myapp@sha256:abc123
# vexctl: sign and attach to a container image as an in-toto attestation
vexctl attest --attest --sign \
--product="oci://ghcr.io/myorg/myapp:1.0" \
.vex/myapp-1.0.openvex.jsonAqua VEX Hub is consumed automatically by Trivy. Once your VEX document is registered there, Trivy suppresses the corresponding false positives for all users of your package globally.
# Write VEX in VEX Hub directory layout
vex-studio assess -p pkg:pypi/mypackage@1.0.0 --vexhub
# This creates:
# .vex/pypi/mypackage/mypackage.openvex.json
# Then follow Aqua VEX Hub registration:
# https://github.com/aquasecurity/vexhub#submitting-vex-documents# Install with dev dependencies
pip install -e ".[dev]"
# Run tests
python -m pytest tests/
# Run tests with coverage
python -m pytest tests/ --cov=vex_studio --cov-report=term-missing
# Lint
ruff check src/
# Type check
mypy src/python -m vex_studio.main --help
python -m vex_studio.main assess -p pkg:pypi/django@3.2.0src/vex_studio/
├── main.py CLI entry point (Typer)
├── config.py AppConfig (pydantic-settings)
├── models/ Pydantic data contracts (SBOM, vulnerability, triage, VEX)
├── parsers/ CycloneDX + SPDX SBOM parsers
├── clients/ OSV.dev + GHSA API clients
├── triage/ CWE questionnaire engine (the core IP)
│ ├── cwe_catalog.py Question bank: CWE -> decision tree
│ ├── questionnaire.py Question selector per CVE
│ ├── decision.py Answer resolver -> VexStatus + justification
│ └── engine.py Interactive triage orchestrator
├── vex/ VEX document generation
│ ├── builder.py OpenVEX direct writer + lib4vex adapter
│ └── hub.py Aqua VEX Hub layout writer
└── cli/
├── commands/ One file per sub-command
└── display.py Rich tables and panels
See Architecture.md for full design documentation.
| Phase | Scope | Status |
|---|---|---|
| 1 | Core CLI: assess, scan, triage, publish (OpenVEX), VEX Hub | ✅ Complete |
| 2 | NVD CWE enrichment, CISA KEV, EPSS, CSAF/CycloneDX output, signing | Planned |
| 3 | FastAPI backend + React web UI, AI-assisted triage | Future |
See PLAN.md for detailed task tracking.
Contributions are welcome. Please open an issue before starting work on a significant change.
The most impactful contribution area is expanding the CWE questionnaire catalog in src/vex_studio/triage/cwe_catalog.py. Currently covers the OWASP/SANS Top 25 CWEs. Adding well-researched questions for additional CWEs directly improves the quality of VEX determinations for maintainers.
# Fork, clone, create a branch
git checkout -b feature/cwe-125-out-of-bounds-read
# Install dev dependencies
pip install -e ".[dev]"
# Make changes and run tests
python -m pytest tests/
ruff check src/
# Open a pull requestApache 2.0 — see LICENSE.
Copyright 2026 SecPod Technologies — secpod.com
Note:
secpod-vex-studiois not yet published to PyPI. Install from source as shown above.