Generate CycloneDX VEX¶
Use vexcalibur generate to write CycloneDX 1.6 VEX JSON from a local CycloneDX or SPDX 3 SBOM, or a GitHub Dependency Graph SBOM.
These examples call an installed vexcalibur. If you don’t have one yet, follow Install Vexcalibur first. Substitute your own SBOM and findings paths throughout.
Choose one inventory input and one finding source:
Input |
Option |
|---|---|
Local CycloneDX JSON or XML, or SPDX 3 JSON-LD |
Positional |
GitHub Dependency Graph SBOM |
|
Finding source |
Option |
|---|---|
Local findings JSON |
|
Private OSV-compatible service |
|
Public OSV |
|
Use local findings¶
Pass a findings file when vulnerability or exploitability analysis already exists locally:
vexcalibur generate \
sbom.json \
--offline \
--findings-file findings.json \
--output /tmp/vexcalibur-vex.json
This mode does not contact OSV. A finding must identify an SBOM component by component_ref or by a package URL that occurs only once. See the local findings format.
When the inventory comes from --github-repo, omit --offline because fetching the SBOM uses the network. --findings-file still selects local findings and prevents an OSV request.
Use a private OSV mirror¶
Point --osv-url at the mirror’s base URL:
vexcalibur generate \
private-sbom.json \
--osv-url https://osv.internal.example \
--output /tmp/vexcalibur-vex.json
The endpoint must provide the OSV /v1/querybatch API used by Vexcalibur. Its
canonical base URL becomes the finding provenance by default. Use the paired
--osv-source-name and --osv-source-url options when the document needs a
public provenance alias instead. See Use a private OSV
mirror for URL rules, alias guidance, limits, and
failure handling.
Use public OSV¶
Warning
The next command sends SBOM package URLs and versions to https://api.osv.dev. Use it only for an inventory approved for public disclosure.
Pass the explicit consent flag:
vexcalibur generate \
sbom.json \
--allow-public-osv \
--output /tmp/vexcalibur-vex.json
Live OSV results change. Add a timestamp when the document metadata must stay stable:
vexcalibur generate \
sbom.json \
--allow-public-osv \
--timestamp 2026-06-23T00:00:00Z \
--output /tmp/vexcalibur-vex.json
A fixed timestamp does not freeze live vulnerability data.
Fetch an SBOM from GitHub¶
Pass --github-repo instead of a local input path:
vexcalibur generate \
--github-repo vexcalibur-dev/vexcalibur \
--allow-public-osv \
--output /tmp/vexcalibur-vex.json
Vexcalibur requests GitHub’s asynchronous SPDX 2.3 JSON report. It waits until the report is ready, then downloads it and extracts package URL references. The resulting components use the same finding and rendering path as a local SBOM.
Fetching the SBOM and querying a vulnerability service are separate network decisions. --github-repo does not grant permission to send the inventory to public OSV.
Vexcalibur resolves GitHub credentials in this order:
the variable named by
--github-token-env.GH_TOKENorGITHUB_TOKENforhttps://api.github.com.gh auth token --hostname HOST, unless--no-gh-authis set.
Public repositories may work without a token, subject to rate limits. A token-backed request needs repository Contents: read permission.
For GitHub Enterprise, pass both the API base URL and an explicit token variable:
vexcalibur generate \
--github-repo internal/example \
--github-api-url https://github.example.test/api/v3 \
--github-token-env GH_ENTERPRISE_TOKEN \
--osv-url https://osv.internal.example \
--output /tmp/vexcalibur-vex.json
If Vexcalibur is already installed in a GitHub Actions job, grant contents: read and use this step excerpt to pass the workflow token:
permissions:
contents: read
steps:
- run: |
vexcalibur generate \
--github-repo "$GITHUB_REPOSITORY" \
--github-token-env GITHUB_TOKEN \
--osv-url https://osv.internal.example \
--output vex.json
env:
GITHUB_TOKEN: ${{ github.token }}
Run it in GitHub Actions¶
A companion Action wraps this command for workflows: vexcalibur-dev/vexcalibur-action.
The Action defines its own inputs and its own pinning, and it publishes a compatibility declaration naming the Vexcalibur version each Action commit was tested against. Those belong to the Action and change on its release schedule, so use its documentation rather than a workflow copied from this page.
Read XML input¶
Pass a CycloneDX XML file in the same position as JSON:
vexcalibur generate \
sbom.xml \
--offline \
--findings-file findings.json \
--output /tmp/vexcalibur-vex.json
Write to standard output¶
Omit --output:
vexcalibur generate \
sbom.json \
--offline \
--findings-file findings.json
Check basic output fields¶
Parse the file and check its format discriminators:
python - <<'PY'
import json
from pathlib import Path
vex = json.loads(Path("/tmp/vexcalibur-vex.json").read_text())
assert vex["bomFormat"] == "CycloneDX"
assert vex["specVersion"] == "1.6"
print(f"found {len(vex.get('vulnerabilities', []))} VEX entries")
PY
This is a sanity check, not validation against the full CycloneDX JSON schema.
See the CLI reference for accepted SBOM versions, size limits, option conflicts, token behavior, and exit messages. See the output reference for grouping and determinism rules.