Command-line interface¶
The package installs two executables:
Command |
Purpose |
|---|---|
|
Primary interface |
|
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 |
|---|---|---|
|
Yes |
One or more values accepted by |
An invalid package URL is rejected before a request is sent.
Options¶
Option |
Default |
Description |
|---|---|---|
|
|
OSV API base URL. Set this for a private mirror. |
|
Off |
Consent to send package URLs to public OSV. |
|
— |
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 |
|
One standard-output line per PURL |
Missing argument or invalid PURL |
|
Typer usage or parameter message |
Public OSV without consent |
|
|
Invalid URL, HTTP failure, bad response, or pagination failure |
|
|
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 |
|---|---|
|
Readable CycloneDX JSON or XML, or SPDX 3 JSON-LD file |
|
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 |
|
Does not construct an OSV client |
Private OSV |
|
Sends inventory to that endpoint |
Public OSV |
|
Sends inventory to |
--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 |
|---|---|---|
|
Standard output |
Write VEX JSON to a file. |
|
— |
On Linux and macOS, atomically write a bounded, versioned generation summary. |
|
Current UTC time |
ISO-8601 document timestamp. |
|
|
Select the output format. |
|
— |
OpenVEX document author; required for OpenVEX. |
|
— |
Optional OpenVEX document author role. |
|
— |
SPDX document creator name; required for SPDX 3. |
|
|
CSAF version; only |
|
— |
CSAF tracking ID; required for CSAF. |
|
— |
CSAF document title; required for CSAF. |
|
— |
CSAF publisher name; required for CSAF. |
|
— |
Normalized absolute HTTP(S) publisher URL; required for CSAF. |
|
— |
CSAF publisher category; required for CSAF. |
|
|
CSAF status: |
|
— |
Local findings JSON. |
|
Off |
Disable network finding sources; requires local findings. |
|
Public OSV when no local findings are selected |
OSV-compatible base URL. |
|
Endpoint-derived |
Public source name alias; requires |
|
Endpoint-derived |
Public HTTPS source URL alias; requires |
|
Off |
Consent to send the inventory to public OSV. |
|
— |
Fetch a GitHub Dependency Graph SBOM instead of reading |
|
|
GitHub REST API base URL. |
|
— |
Read the GitHub token from this environment variable. |
|
Enabled |
Enable or disable fallback to |
|
— |
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 |
|
JSON on standard output or in |
Bad timestamp or unknown option |
|
Typer usage or parameter message |
Missing or conflicting input/source options |
|
|
Invalid local SBOM or unqueryable inventory |
|
|
GitHub configuration, request, or SPDX failure |
|
|
Invalid local findings |
|
|
Public OSV without consent or invalid OSV URL |
|
|
OSV request or response failure |
|
|
Findings cannot form the selected VEX format |
|
|
Missing or nonreplaceable VEX output destination in report mode |
|
|
Output write failure |
|
|
Unsafe, missing-parent, or unsupported execution report destination |
|
|
Execution report requested on Windows |
|
|
Execution report construction failure |
|
|
Execution report write failure |
|
|
Output finalization or cleanup failure |
|
|
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 |
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 for the current shell. |
|
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 |
|---|---|---|
|
— |
Accepted but not read. Legacy credentials and sources are ignored. |
|
Required |
CycloneDX JSON or XML, or SPDX 3 JSON-LD path. Standard input ( |
|
|
Only |
|
|
Only |
|
|
Output path. Use |
|
Off |
Replace an existing output file. |
|
Off |
Accepted; there is no progress output to suppress. |
|
Off |
Print compatibility diagnostics to standard error. |
|
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 |
|
JSON on standard output or in the selected file |
Missing input, standard-input request, unsupported format/schema, existing output, or bad timestamp |
|
|
Conflicting source options |
|
|
Invalid SBOM |
|
|
Invalid local findings |
|
|
Public OSV without consent or invalid OSV URL |
|
|
OSV request or response failure |
|
|