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 INPUT_FILE

GitHub Dependency Graph SBOM

--github-repo OWNER/REPO

Finding source

Option

Local findings JSON

--findings-file PATH; add --offline for a local SBOM

Private OSV-compatible service

--osv-url URL

Public OSV

--allow-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:

  1. the variable named by --github-token-env.

  2. GH_TOKEN or GITHUB_TOKEN for https://api.github.com.

  3. gh auth token --hostname HOST, unless --no-gh-auth is 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.