CLI Reference

Vexcalibur is usable for the commands documented here, but public contracts are not stable before 1.0. Command names, flags, output fields, and exit behavior can change before the first stable compatibility policy.

The installed package exposes two executables:

  • vexcalibur: the primary command.

  • vexy: a compatibility command for a documented subset of legacy vexy invocations.

Run uv run --frozen vexcalibur --help or uv run --frozen vexcalibur COMMAND --help for the exact help text generated by the current Typer command definitions.

Expected input and configuration errors are reported without Python tracebacks. Treat any nonzero exit code as a failed command in automation.

vexcalibur query-osv

Query OSV for one or more package URLs and print vulnerability IDs.

uv run --frozen vexcalibur query-osv PURL... [OPTIONS]

Arguments:

  • PURL...: one or more package URLs.

Package URL constraints:

  • Each value must parse as a package URL using packageurl-python.

  • The command sends each package URL to the OSV query batch API as the package purl value.

  • Include a package version in the PURL when the OSV query should be version specific, for example pkg:pypi/django@1.2.

  • Invalid package URL strings are rejected before any OSV request is made.

Options:

  • --osv-url TEXT: OSV API base URL. Use this for private OSV mirrors. Defaults to https://api.osv.dev.

  • --allow-public-osv: allow sending package URLs to the public OSV API.

Network behavior:

  • The default OSV URL is https://api.osv.dev.

  • Public OSV queries fail unless --allow-public-osv is present.

  • Private mirrors do not require --allow-public-osv; pass the mirror base URL with --osv-url.

  • --osv-url must be an absolute https:// URL with a hostname. Cleartext http:// is accepted only for loopback hosts such as 127.0.0.1, ::1, or localhost. Do not include userinfo, query strings, or fragments.

  • The OSV endpoint must support the /v1/querybatch response shape used by the public OSV API.

The public OSV API requires explicit opt-in:

uv run --frozen vexcalibur query-osv pkg:pypi/django@1.2 --allow-public-osv

Illustrative output shape:

pkg:pypi/django@1.2: VULN-ID-1, VULN-ID-2

Live OSV data changes over time, so vulnerability IDs, counts, and ordering can change. If OSV returns no matches for an input, the line uses this shape:

pkg:pypi/example@1.0.0: no vulnerabilities found

For private package inventories, use a private mirror:

uv run --frozen vexcalibur query-osv \
  pkg:pypi/example@1.0.0 \
  --osv-url https://osv.internal.example

query-osv exit behavior:

Condition

Exit code

Output stream and message shape

All package URLs are queried successfully

0

Standard output contains one result line per input PURL.

Missing PURL... argument

nonzero Typer usage error

Typer prints a missing-argument error.

Invalid package URL

nonzero Typer parameter error

Typer includes not a valid package URL.

Public OSV URL without --allow-public-osv

1

Standard error starts with OSV query failed: and mentions --allow-public-osv.

Invalid or unsafe --osv-url

1

Standard error starts with OSV query failed: and names the URL constraint.

OSV HTTP, network, pagination, or response-shape failure

1

Standard error starts with OSV query failed:.

Verification commands:

uv run --frozen vexcalibur query-osv pkg:pypi/django@1.2 --allow-public-osv

Expected success signal: exit code 0 and one output line for the submitted PURL.

uv run --frozen vexcalibur query-osv pkg:pypi/django@1.2

Expected failure signal: nonzero exit and an OSV query failed: message that asks for --allow-public-osv.

vexcalibur generate

Generate CycloneDX VEX JSON from a local CycloneDX SBOM file or from a GitHub Dependency Graph SBOM.

uv run --frozen vexcalibur generate [INPUT_FILE] [OPTIONS]

Arguments:

  • INPUT_FILE: readable CycloneDX JSON or XML SBOM file. Omit this argument when using --github-repo.

Exactly one package inventory source is required:

  • INPUT_FILE for a local CycloneDX JSON or XML SBOM.

  • --github-repo OWNER/REPO for the repository’s GitHub Dependency Graph SBOM.

JSON input must be UTF-8. XML input must be rooted at bom in the http://cyclonedx.org/schema/bom/1.4, /1.5, or /1.6 namespace. XML may use parser-detected XML encodings such as UTF-8 or UTF-16. DTD, entity, and external-reference declarations are rejected.

GitHub Dependency Graph input uses GitHub’s asynchronous SBOM REST API to request and download an SPDX 2.3 JSON report. Vexcalibur extracts package URL references from that document and passes them through the same provider-neutral VEX generation path used for local CycloneDX files.

Options:

  • --output PATH, -o PATH: write VEX JSON to a file instead of standard output.

  • --timestamp TEXT: ISO-8601 timestamp for deterministic output metadata.

  • --findings-file PATH: local Vexcalibur findings JSON file. When set, no OSV API request is sent.

  • --offline: disable network vulnerability sources. Currently requires --findings-file and cannot be combined with --github-repo.

  • --osv-url TEXT: OSV API base URL. Use this for private OSV mirrors. Defaults to https://api.osv.dev.

  • --allow-public-osv: allow sending SBOM package URLs and versions to the public OSV API.

  • --github-repo TEXT: fetch the GitHub Dependency Graph SBOM for OWNER/REPO instead of reading a local SBOM file.

  • --github-api-url TEXT: GitHub API base URL for --github-repo. Defaults to https://api.github.com.

  • --github-token-env TEXT: environment variable containing a GitHub token. Use this explicitly for non-default GitHub API hosts.

  • --gh-auth / --no-gh-auth: allow or disable fallback to gh auth token when no GitHub token environment variable is set. Enabled by default.

The command fails closed for public OSV:

uv run --frozen vexcalibur generate tests/fixtures/sbom/cyclonedx-json-simple.json

Pass --allow-public-osv only when the SBOM inventory is safe to send to the public OSV API:

uv run --frozen vexcalibur generate \
  tests/fixtures/sbom/cyclonedx-json-simple.json \
  --allow-public-osv \
  --output /tmp/vexcalibur-vex.json

For private SBOMs, use an internal OSV-compatible endpoint:

uv run --frozen vexcalibur generate \
  path/to/sbom.json \
  --osv-url https://osv.internal.example \
  --output /tmp/vexcalibur-vex.json

For offline workflows, use local findings:

uv run --frozen vexcalibur generate \
  path/to/sbom.json \
  --offline \
  --findings-file path/to/findings.json \
  --output /tmp/vexcalibur-vex.json

--findings-file cannot be combined with --allow-public-osv or --osv-url.

To generate from a GitHub repository’s Dependency Graph SBOM, omit INPUT_FILE and pass --github-repo:

uv run --frozen vexcalibur generate \
  --github-repo vexcalibur-dev/vexcalibur \
  --allow-public-osv \
  --output /tmp/vexcalibur-vex.json

Public GitHub repositories can be requested without a token, subject to GitHub API rate limits. For authenticated requests, Vexcalibur resolves a token in this order:

  • the environment variable named by --github-token-env;

  • GH_TOKEN or GITHUB_TOKEN for https://api.github.com;

  • gh auth token --hostname HOST, unless --no-gh-auth is set.

Use --github-api-url with GitHub Enterprise API hosts:

uv run --frozen 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

--github-api-url must use HTTPS and must not include userinfo, query strings, or fragments. Token-backed GitHub SBOM requests need repository Contents: read permission. Public repositories can be requested without a token, subject to GitHub API rate limits.

Fetching the SBOM from GitHub is a network operation, so --github-repo cannot be combined with --offline. Sending the resulting package inventory to public OSV is a separate network decision and still requires --allow-public-osv.

generate output behavior:

  • Without --output, the command writes CycloneDX 1.6 VEX JSON to standard output.

  • With --output, the command writes the VEX JSON file and does not echo it to standard output.

  • OSV-derived findings are marked in_triage until policy-driven state selection is implemented.

  • Local findings can supply explicit CycloneDX VEX analysis states.

generate exit behavior:

Condition

Exit code

Output stream and message shape

VEX JSON generated successfully

0

Standard output contains VEX JSON unless --output is set.

Invalid timestamp

nonzero Typer parameter error

Typer prints the parameter error.

Missing package inventory source

1

Standard error starts with Invalid generate options: and mentions INPUT_FILE or --github-repo.

Mutually incompatible source flags

1

Standard error starts with Invalid generate options:.

Unsupported, invalid, unsafe, or unqueryable SBOM

1

Standard error starts with SBOM ingest failed:.

GitHub SBOM request, authentication, API, or SPDX parsing error

1

Standard error starts with GitHub SBOM ingest failed:.

Local findings file error

1

Standard error starts with Local findings ingest failed:.

Empty --osv-url

1

Standard error starts with Invalid generate options: and says --osv-url must not be empty.

Public OSV URL without --allow-public-osv

1

Standard error starts with VEX generation failed: and mentions --allow-public-osv.

Invalid or unsafe --osv-url

1

Standard error starts with VEX generation failed: and names the URL constraint.

OSV HTTP, network, pagination, or response-shape failure

1

Standard error starts with OSV query failed:.

Output file cannot be written

1

Standard error starts with Could not write VEX output.

Verification command:

uv run --frozen vexcalibur generate \
  tests/fixtures/sbom/cyclonedx-json-simple.json \
  --allow-public-osv \
  --timestamp 2026-06-23T00:00:00Z \
  --output /tmp/vexcalibur-vex.json
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"
assert vex["metadata"]["timestamp"] == "2026-06-23T00:00:00+00:00"
print("generated CycloneDX VEX")
PY

Expected success signal: the Python verification command prints generated CycloneDX VEX.

See CycloneDX VEX output for the generated document shape, grouping rules, affected component behavior, and deterministic output contract.

vexy

vexy is a compatibility executable for migrating selected legacy workflows to Vexcalibur. It does not reimplement the old Sonatype OSS Index integration, old CycloneDX XML VEX output, or old CycloneDX 1.4 VEX output. It maps supported legacy-style flags onto Vexcalibur’s current CycloneDX 1.6 JSON generator.

uv run --frozen vexy [OPTIONS]

Supported compatibility flags:

  • -c PATH, --config PATH: accepted so legacy command lines still parse. Vexcalibur does not read this file or use legacy data-source credentials.

  • -i PATH, --in-file PATH: CycloneDX JSON or XML SBOM file. -i - stdin input is rejected.

  • --format json: required output format. --format xml is rejected.

  • --schema-version 1.6: required VEX schema version. Legacy --schema-version 1.4 is rejected.

  • -o PATH, --o PATH, --output PATH: write VEX JSON to a file. Use --output - for standard output. When this option is omitted, vexy writes cyclonedx-vex.json in the current working directory.

  • --force: overwrite an existing output file.

  • -q: accepted for compatibility. The compatibility command does not print progress output.

  • -X: print compatibility debug output to standard error.

Vexcalibur source-mode flags are also available:

  • --findings-file PATH: local Vexcalibur findings JSON file. When set, no OSV API request is sent.

  • --offline: disable network sources. Currently requires --findings-file.

  • --osv-url TEXT: OSV-compatible API base URL for a private mirror.

  • --allow-public-osv: allow sending SBOM package URLs and versions to https://api.osv.dev.

  • --timestamp TEXT: ISO-8601 timestamp for deterministic output metadata.

The compatibility command preserves Vexcalibur’s public-service boundary. A legacy -c config containing OSV or OSS Index sources is not enough to contact a public service. Use --allow-public-osv only when the SBOM inventory is safe to send to public OSV, or use an absolute https:// --osv-url for a private OSV-compatible endpoint.

Offline migration example:

uv run --frozen vexy \
  -c tests/fixtures/vexy/legacy-config.yml \
  -i tests/fixtures/sbom/cyclonedx-xml-1.5-simple.xml \
  --format json \
  --schema-version 1.6 \
  --output - \
  --offline \
  --findings-file tests/fixtures/findings/all-analysis-states.json \
  --timestamp 2026-06-23T00:00:00Z

Expected success signal: exit code 0 and CycloneDX 1.6 VEX JSON on standard output.

vexy exit behavior:

Condition

Exit code

Output stream and message shape

VEX JSON generated successfully with --output -

0

Standard output contains CycloneDX 1.6 VEX JSON.

VEX JSON generated successfully with a file output

0

The output file contains CycloneDX 1.6 VEX JSON.

Missing -i/--in-file

1

Standard error starts with vexy compatibility failed:.

Unsupported legacy output format or schema version

1

Standard error starts with vexy compatibility failed:.

-i - stdin input

1

Standard error says stdin input is not supported.

Output file exists without --force

1

Standard error asks for --force.

--offline without --findings-file

1

Standard error starts with vexy compatibility failed: and mentions --findings-file.

Empty --osv-url

1

Standard error starts with vexy compatibility failed: and says --osv-url must not be empty.

--findings-file combined with --allow-public-osv or --osv-url

1

Standard error starts with vexy compatibility failed: and identifies the incompatible option.

Public OSV URL without --allow-public-osv

1

Standard error starts with VEX generation failed: and mentions --allow-public-osv.

Invalid or unsafe --osv-url

1

Standard error starts with VEX generation failed: and names the URL constraint.

OSV HTTP, network, pagination, or response-shape failure

1

Standard error starts with OSV query failed:.