Command-line interface

The package installs two executables:

Command

Purpose

vexcalibur

Primary interface

vexy

Compatibility interface for a limited set of legacy invocations

Before 1.0, pin an exact release. The compatibility policy defines the command, default, exit-status, and machine-readable output guarantees that begin at 1.0. Human-readable messages are not parsing contracts.

Run vexcalibur --help or vexcalibur COMMAND --help for help generated from the installed version. Expected input and source errors are printed without a Python traceback. Automation should treat every nonzero status as failure.

vexcalibur query-osv

Queries an OSV-compatible service for one or more package URLs.

vexcalibur query-osv [OPTIONS] PURL...

Arguments

Argument

Required

Description

PURL...

Yes

One or more values accepted by packageurl-python. Include a version for a version-specific query.

An invalid package URL is rejected before a request is sent.

Options

Option

Default

Description

--osv-url TEXT

https://api.osv.dev

OSV API base URL. Set this for a private mirror.

--allow-public-osv

Off

Consent to send package URLs to public OSV.

--help

Print help and exit.

The public default fails unless --allow-public-osv is present. A private endpoint does not require that flag.

An OSV URL must use HTTPS and include a hostname. HTTP is accepted only for a loopback host such as localhost, 127.0.0.1, or ::1. User information, query strings, and fragments are rejected. The endpoint must implement /v1/querybatch.

Success produces one line per input:

pkg:pypi/django@1.2: VULN-ID-1, VULN-ID-2
pkg:pypi/example@1.0.0: no vulnerabilities found

Live IDs and ordering may change.

IDs are printed as literal text rather than Rich markup. The response parser normalizes Unicode canonical equivalents and rejects IDs containing terminal controls, bidi controls, or line separators before they can reach a terminal, CI log, or VEX document.

Exit behavior

Condition

Status

Error prefix or output

All queries succeed

0

One standard-output line per PURL

Missing argument or invalid PURL

2

Typer usage or parameter message

Public OSV without consent

1

OSV query failed:

Invalid URL, HTTP failure, bad response, or pagination failure

1

OSV query failed:

vexcalibur generate

Generates CycloneDX 1.6, OpenVEX 0.2.0, CSAF 2.0, or SPDX 3.0.1 VEX JSON.

vexcalibur generate [OPTIONS] [INPUT_FILE]

Inventory input

Provide exactly one inventory source:

Input

Description

INPUT_FILE

Readable CycloneDX JSON or XML, or SPDX 3 JSON-LD file

--github-repo OWNER/REPO

GitHub Dependency Graph SBOM

Local input accepts CycloneDX 1.4, 1.5, and 1.6, and SPDX 3.0.1 JSON-LD. JSON must be UTF-8. XML content is CycloneDX and must have a CycloneDX bom root in the matching namespace. Parser-detected encodings such as UTF-16 are accepted. A JSON document selects its format from one top-level marker: bomFormat for CycloneDX or @graph for SPDX 3. A document carrying both markers is rejected instead of guessed.

XML input rejects DTD, entity, and external-reference declarations.

Local inventory input must resolve to a regular file. A symbolic link to a regular file is accepted; FIFOs, devices, sockets, directories, and links to those objects are rejected before content is read. Vexcalibur reads at most 10 MiB from the same opened descriptor that it inspects. GitHub report downloads have the same byte limit.

JSON input rejects duplicate object keys, more than 100 nested arrays or objects, and integer literals longer than 1,000 decimal digits. A document may contain at most 10,000 components. CycloneDX component nesting is limited to 50 levels. Parsed components with package URLs must have unique references.

SPDX 3 input must declare the SPDX 3.0.1 JSON-LD context string as its @context. The loader reads software_Package elements from @graph, including the derived ai_AIPackage and dataset_DatasetPackage types: the package URL comes from software_packageUrl or an externalIdentifier entry of type packageUrl (inline, or a reference to an ExternalIdentifier in @graph), equivalent values collapse, and distinct values for one package are rejected. The spdxId becomes the component reference, software_packageVersion supplies a version for an unversioned package URL, and packages without package URLs are omitted.

This extracts package identities from the supported compact JSON form; it does not validate the full SPDX document or perform general JSON-LD expansion. Every graph node must have a string type. Contexts and referenced documents are never fetched. A missing or blank spdxId falls back to the canonical package URL; otherwise its outer whitespace is removed.

Package definitions must be top-level @graph entries; inline packages are rejected. External identifier references must resolve locally, even when a direct package URL is present. Duplicate external identifier node IDs are rejected. Expanded canonical package URLs are limited to 10 MiB across all packages, counting repeated references once per package.

GitHub input requests an asynchronous SPDX 2.3 JSON report and extracts package URL references. The repository package itself and packages without package URLs are omitted. A package with multiple distinct package URL references is rejected.

--github-repo selects a repository, not a commit, tag, build, or deployed environment. Its dependency graph may include development dependencies and entries retained from removed manifests. Vexcalibur does not compare that inventory with a checkout, discard entries it considers stale, or filter it to runtime dependencies.

For VEX about a particular artifact, supply a CycloneDX SBOM generated for that artifact. Input validation and a successful execution report do not prove that an inventory matches the artifact. See Inventory scope.

Finding source

Choose one source mode:

Mode

Options

Network behavior

Local findings

--findings-file PATH; normally paired with --offline

Does not construct an OSV client

Private OSV

--osv-url URL

Sends inventory to that endpoint

Public OSV

--allow-public-osv

Sends inventory to https://api.osv.dev

--offline currently requires --findings-file. A findings file cannot be combined with --osv-url, either --osv-source-* option, or --allow-public-osv.

Public OSV findings identify their source as OSV at https://osv.dev/. A custom endpoint instead uses OSV-compatible mirror and its canonicalized base URL. To keep a private endpoint out of the document, provide both --osv-source-name and --osv-source-url as a public provenance alias. Vexcalibur never invents that alias. The alias URL must be absolute HTTPS and must not contain credentials, a query, or a fragment. The OSV name and every HTTPS URL on the official osv.dev origin are reserved for canonical public OSV provenance.

OSV generation needs at least one versioned component with a package URL. A version may come from the PURL, CycloneDX version, GitHub SPDX versionInfo, or SPDX 3 software_packageVersion. When an inventory supplies both an explicit version and a PURL version, their decoded values must match. The command rejects a contradiction and fails instead of treating an empty query set as authoritative.

An explicit empty local findings array is valid for CycloneDX output. OpenVEX, CSAF, and SPDX 3 reject it because their standalone VEX documents need at least one statement or vulnerability assertion.

Options

Option

Default

Description

--output PATH, -o PATH

Standard output

Write VEX JSON to a file.

--execution-report PATH

On Linux and macOS, atomically write a bounded, versioned generation summary.

--timestamp TEXT

Current UTC time

ISO-8601 document timestamp.

--format cyclonedx|openvex|csaf|spdx3

cyclonedx

Select the output format.

--author TEXT

OpenVEX document author; required for OpenVEX.

--author-role TEXT

Optional OpenVEX document author role.

--creator TEXT

SPDX document creator name; required for SPDX 3.

--csaf-version TEXT

2.0

CSAF version; only 2.0 is accepted.

--csaf-document-id TEXT

CSAF tracking ID; required for CSAF.

--csaf-document-title TEXT

CSAF document title; required for CSAF.

--csaf-publisher-name TEXT

CSAF publisher name; required for CSAF.

--csaf-publisher-namespace TEXT

Normalized absolute HTTP(S) publisher URL; required for CSAF.

--csaf-publisher-category TEXT

CSAF publisher category; required for CSAF.

--csaf-document-status TEXT

draft

CSAF status: draft, final, or interim.

--findings-file PATH

Local findings JSON.

--offline

Off

Disable network finding sources; requires local findings.

--osv-url TEXT

Public OSV when no local findings are selected

OSV-compatible base URL.

--osv-source-name TEXT

Endpoint-derived

Public source name alias; requires --osv-source-url.

--osv-source-url TEXT

Endpoint-derived

Public HTTPS source URL alias; requires --osv-source-name.

--allow-public-osv

Off

Consent to send the inventory to public OSV.

--github-repo OWNER/REPO

Fetch a GitHub Dependency Graph SBOM instead of reading INPUT_FILE.

--github-api-url TEXT

https://api.github.com

GitHub REST API base URL.

--github-token-env NAME

Read the GitHub token from this environment variable.

--gh-auth, --no-gh-auth

Enabled

Enable or disable fallback to gh auth token.

--help

Print help and exit.

The GitHub API URL must use HTTPS and must not contain user information, a query string, or a fragment. For GitHub Enterprise, pass its API base path, such as https://github.example.test/api/v3.

When --github-token-env is absent, token lookup checks GH_TOKEN and GITHUB_TOKEN for api.github.com. It then tries gh auth token --hostname HOST if fallback is enabled.

Public repositories may work anonymously. Token-backed requests need repository Contents: read permission.

--github-repo cannot be combined with --offline because fetching the SBOM uses the network. Public OSV consent remains separate.

--author and --author-role are valid only with --format openvex. --creator is valid only with --format spdx3, which requires it.

The --csaf-* options are valid only with --format csaf. CSAF requires the document ID, title, publisher name, publisher namespace, and publisher category. The namespace must be an absolute normalized HTTP(S) URL controlled by the publisher. Use ASCII RFC 3986 syntax, with IDNA for internationalized hosts and percent encoding for non-ASCII path characters. Publisher category accepts coordinator, discoverer, other, user, or vendor; translator is not supported. Document IDs cannot contain line terminators.

Format metadata is checked before Vexcalibur fetches a GitHub SBOM or queries OSV.

Output

Without --output, JSON goes to standard output. With it, the command writes the file and prints no success message.

Generation rejects serialized output larger than 25 MiB, measured as UTF-8, before writing it to standard output or a file. Built-in renderers first apply an allocation-free conservative estimate for repeated, escaped, and derived strings. That estimate can reject an input whose eventual grouped document would be smaller than 25 MiB. Every renderer, including a custom Python renderer, remains subject to the exact post-render UTF-8 limit.

--output overwrites an existing file without prompting. Its parent directory must already exist. When --execution-report is absent, the write is not atomic.

When report mode also uses --output, Vexcalibur binds both paths to verified parent directories on Linux and macOS, then replaces them from private temporary files. Each published file has mode 0600; existing ownership and mode are not preserved. Replacing a symbolic link or hard-link path replaces that directory entry rather than writing through it. An existing output or report path must be a regular file or symbolic link. Directories, FIFOs, sockets, and devices fail before generation with a Could not prepare generate outputs: error. The parent filesystem must support descriptor-relative file operations and a directory fsync. A failure to flush a directory makes the command fail.

Without --output, VEX bytes go directly to standard output and are flushed before the report is published. Vexcalibur does not stage that stream and cannot set the mode of a shell redirection target.

The vexy compatibility command differs: it refuses an existing file unless --force is present.

CSAF file output also enforces the standard basename derived from the document ID. Lowercase the ID, replace each run matching [^+\-a-z0-9]+ with one underscore, and append .json. For example, ACME VEX:2026/001 requires acme_vex_2026_001.json. The rule does not apply to standard output.

OSV-derived entries use analysis state in_triage. Local findings may set any supported domain state.

--execution-report writes a bounded JSON summary after the selected VEX document has been rendered and emitted. The summary contains counts, source categories, the output format, the installed package version, and the exact document digest and byte size. It omits package names and URLs, vulnerability IDs, repository names, filesystem paths, provider URLs, credentials, and exception text.

The option fails on Windows before it touches the requested report. See the generation execution report for the schema and failure behavior.

CycloneDX output preserves those state names. OpenVEX maps them to its four-status model and requires state-specific evidence. CSAF maps them to product-status lists and requires product-scoped remediation or impact evidence where the VEX profile calls for it. SPDX 3 maps them to the security profile’s four VEX relationship classes with the same evidence rules as OpenVEX. OpenVEX and SPDX 3 reject nonidentical assertions for one vulnerability and product. CSAF can group same-status provenance and evidence, but rejects contradictory effective statuses for that pair. Read the CycloneDX, OpenVEX, CSAF, or SPDX 3 output reference for the exact contract.

Exit behavior

Condition

Status

Error prefix

Generation succeeds

0

JSON on standard output or in --output

Bad timestamp or unknown option

2

Typer usage or parameter message

Missing or conflicting input/source options

1

Invalid generate options:

Invalid local SBOM or unqueryable inventory

1

SBOM ingest failed:

GitHub configuration, request, or SPDX failure

1

GitHub SBOM ingest failed:

Invalid local findings

1

Local findings ingest failed:

Public OSV without consent or invalid OSV URL

1

VEX generation failed:

OSV request or response failure

1

OSV query failed:

Findings cannot form the selected VEX format

1

VEX generation failed:

Missing or nonreplaceable VEX output destination in report mode

1

Could not prepare generate outputs:

Output write failure

1

Could not write VEX output

Unsafe, missing-parent, or unsupported execution report destination

1

Could not prepare generate outputs:

Execution report requested on Windows

1

Could not prepare generate outputs:

Execution report construction failure

1

Could not create execution report:

Execution report write failure

1

Could not write execution report

Output finalization or cleanup failure

1

Could not finalize generate outputs:

A finalization failure can leave the VEX document in place. Vexcalibur removes its published report when it can, but a cleanup failure makes either destination indeterminate. Treat the operation as failed and inspect both paths before reusing the directory.

Resource limits

The CLI uses these fixed client defaults:

Boundary

Default limit

OSV request I/O

30-second HTTPX timeout per request phase; no automatic retry

Complete OSV client operation

120-second wall-clock deadline

One encoded OSV response, including an error or redirect body

8 MiB

Encoded OSV responses across one client operation

64 MiB

One decoded OSV response, including an error or redirect body

8 MiB

Decoded OSV responses across one client operation

64 MiB

OSV pagination

100 rounds; page tokens no longer than 4,096 characters

OSV queries

10,000 per operation; requests are split into ordered chunks of at most 1,000

OSV vulnerability data

IDs no longer than 512 characters; 10,000 unique IDs per query; 100,000 query-and-ID results per operation

OSV-to-component expansion

100,000 findings, checked before findings are materialized

Built-in pre-render estimate and exact serialized VEX

25 MiB UTF-8 budget; the conservative estimate may reject earlier

Generation execution report

16 KiB UTF-8, including its trailing newline

Report-mode coordination lock

10-second wait for each destination directory or per-report stdout sequence lock

GitHub SBOM API and report download

30 seconds per request; at most 30 report polls; one-second default delay; numeric Retry-After capped at 10 seconds; report polling is the only retry

OsvClient callers may lower or raise its constructor limits. Encoded and decoded response limits are configured independently. The 1,000-query chunk size, 100,000-finding expansion limit, and 25 MiB output limit are fixed generation boundaries in this release.

Shell completion

Both executables provide Typer’s completion options:

Option

Behavior

--install-completion

Install completion for the current shell.

--show-completion

Print the completion script for review or customization.

Use the option on the executable itself, for example vexcalibur --show-completion.

vexy

The compatibility executable maps selected legacy-style flags to the current generator. It writes CycloneDX 1.6 JSON only.

vexy [OPTIONS]

It does not restore Sonatype OSS Index behavior, CycloneDX XML VEX output, or CycloneDX 1.4 VEX output.

Compatibility options

Option

Default

Behavior

-c PATH, --config PATH

Accepted but not read. Legacy credentials and sources are ignored.

-i PATH, --in-file PATH

Required

CycloneDX JSON or XML, or SPDX 3 JSON-LD path. Standard input (-) is rejected.

--format TEXT

json

Only json is accepted.

--schema-version TEXT

1.6

Only 1.6 is accepted.

-o PATH, --o PATH, --output PATH

cyclonedx-vex.json

Output path. Use - for standard output.

--force

Off

Replace an existing output file.

-q

Off

Accepted; there is no progress output to suppress.

-X

Off

Print compatibility diagnostics to standard error.

--timestamp TEXT

Current UTC time

ISO-8601 document timestamp.

The current Vexcalibur source options are also accepted: --findings-file, --offline, --osv-url, --osv-source-name, --osv-source-url, and --allow-public-osv. The same trust boundary and option conflicts apply as for vexcalibur generate.

Offline migration example

vexy \
  -c legacy-config.yml \
  -i sbom.xml \
  --format json \
  --schema-version 1.6 \
  --output - \
  --offline \
  --findings-file findings.json \
  --timestamp 2026-06-23T00:00:00Z

Success writes CycloneDX 1.6 JSON to standard output.

Exit behavior

Condition

Status

Error prefix or output

Generation succeeds

0

JSON on standard output or in the selected file

Missing input, standard-input request, unsupported format/schema, existing output, or bad timestamp

1

vexy compatibility failed:

Conflicting source options

1

vexy compatibility failed:

Invalid SBOM

1

SBOM ingest failed:

Invalid local findings

1

Local findings ingest failed:

Public OSV without consent or invalid OSV URL

1

VEX generation failed:

OSV request or response failure

1

OSV query failed: