waybill follows a strict waybill <noun> <verb> pattern. This page is the canonical
operator reference: every flag accepted at every level, with type, default, repeatable
status, valid value vocab where applicable, a one-paragraph description, and at least
one example invocation.
Top-level nouns:
sbom— SBOM generation, enrichment, verification, and parity (stable)policy— in-toto layout generation (stable)attestation— attestation management (stable)trace— eBPF build-process tracing (experimental, Linux only)
Experimental means: the output formats are stable, but the trace-mode pipeline adds ~2-3× wall-clock overhead on syscall-heavy builds, requires CAP_BPF + CAP_PERFMON, and has coverage gaps on some syscall variants (
openat2,io_uring). For most SBOM use cases preferwaybill sbom scan— it produces richer output with no privilege requirements and runs on any OS.
Global flags apply to every subcommand and must appear before the noun:
waybill --offline sbom scan --path .This reference documents every operator-facing flag exposed by --help. Internal
debugging flags (those gated behind --debug-* or off the --help listing) are
intentionally absent. To verify the reference is in sync with the binary's actual
flag set, run bash scripts/verify-docs-currency.sh (exit 0 = in sync).
Deprecated flags are marked with a **Deprecated:** block listing the milestone
they were deprecated in, the replacement, and the removal target if scheduled.
These flags can be passed before any subcommand. They are also accepted after the noun on every subcommand (clap's flag-position-tolerant parser).
| Flag | Type | Default | Description |
|---|---|---|---|
--offline |
bool | off | Disable all outbound network calls (deps.dev, ClearlyDefined). |
--exclude-scope <SCOPE> |
enum (repeatable, comma-separated) | (none) | Drop components whose lifecycle scope matches any listed value. Valid: dev, build, test. Runtime scope is always retained. |
--include-declared-deps |
bool | off (--image) / on (--path) |
Include declared-but-not-on-disk dependencies (manifest SBOM mode). |
--include-legacy-rpmdb |
bool | off | Read legacy Berkeley-DB rpmdb on pre-RHEL-8 / CentOS-7 / Amazon-Linux-2 images. Also enabled via WAYBILL_INCLUDE_LEGACY_RPMDB=1. |
--timeout <SECONDS> |
u64 | disabled | Wall-clock time limit for the entire waybill invocation. Exits with status 124 (POSIX timeout(1) convention) when exceeded. Set to 0 (or omit) to disable. |
--no-go-mod-why |
bool | off | Disable Go build-graph classification via go mod why. Also via WAYBILL_NO_GO_MOD_WHY=1. |
--exclude-path <PATH_OR_PATTERN> |
string (repeatable) | (none) | Skip directory subtrees matching the given path or glob pattern during scan. Entries with */?/[ are patterns at any depth; otherwise literal paths anchored at scan root. Also via WAYBILL_EXCLUDE_PATH. See --exclude-path. |
--supplement-cdx <PATH> |
path | (none) | Merge an operator-supplied CDX 1.6 (or 1.4 / 1.5) JSON document declaring ground truth the scanner cannot observe: SaaS dependencies, vendored libraries without manifests, license / supplier / copyright metadata. See --supplement-cdx. |
Disable all outbound HTTP. When set, deps.dev license/CPE lookups, deps.dev hash queries, and ClearlyDefined enrichment all become no-ops. The scanner still produces a complete SBOM from local sources (lockfiles, package databases, manifests). Useful for air-gapped scanners, reproducible-build environments, and CI lanes that can't reach the internet.
waybill --offline sbom scan --path . --output offline.cdx.jsonAccepts three equivalent forms:
--offline— alone, equivalent to--offline=true--offline=true— explicit on (handy for scripts toggled by a boolean variable)--offline=false— explicit off (overrides aWAYBILL_OFFLINE=1environment default for a single invocation)
The = is required when a value is supplied; --offline true (with a space)
is rejected so the next positional argument is never silently consumed.
See also: Configuration for the full offline-mode contract.
Drop components whose lifecycle scope matches any of the listed values. Comma-
separated. Valid values: dev, build, test. Runtime-scope is always
retained — excluding all of runtime would produce an empty SBOM.
--exclude-scope dev,build,test produces the strict "what shipped to production"
view. --exclude-scope test drops only test deps; --exclude-scope dev,build
keeps test deps for security-audit workflows.
waybill sbom scan --path . --exclude-scope dev,build,test --output runtime.cdx.jsonWhen omitted, waybill emits all scopes (Runtime + Development + Build + Test).
Include declared-but-not-on-disk dependencies (manifest SBOM). By default,
waybill emits only components physically present in the scanned tree or image
("artifact SBOM" — if it's in the image, it's in the SBOM). When set, also
emits: deps.dev-reported transitives with no on-disk trace
(source_type = declared-not-cached); Maven pom.xml-declared direct deps
with no matching JAR or .m2 cache entry (source_type = workspace); Maven
BFS cache-miss transitives (source_type = transitive, no .pom on disk).
Auto-enabled for sbom scan --path so source-tree scans keep the "what would
be pulled in on build" view; explicit for --image when you want the same
permissive output from a container scan.
See docs/design-notes.md for the full artifact-vs-manifest SBOM rationale.
Enable reading of the legacy Berkeley-DB rpmdb (/var/lib/rpm/Packages) on
pre-RHEL-8 / CentOS-7 / Amazon-Linux-2 images. Off by default. Also enabled
via WAYBILL_INCLUDE_LEGACY_RPMDB=1.
waybill sbom scan --image centos7.tar --include-legacy-rpmdb --output centos7.cdx.jsonModern RHEL-8+ / Fedora / Amazon-Linux-2023 images use the SQLite rpmdb
(/var/lib/rpm/rpmdb.sqlite) which waybill reads by default; the BDB format
is opt-in to keep the modern hot-path slim.
Wall-clock time limit for the entire waybill invocation, in seconds. If
exceeded, waybill emits a tracing::error to stderr and exits with status
124 (POSIX timeout(1)
convention). Disabled when omitted or set to 0.
waybill --timeout 600 sbom scan --image registry.example.com/big-image:latest --output big.cdx.json
echo "exit: $?" # 124 if the scan ran longer than 600s, 0 otherwiseUse cases:
- CI: bound a runaway scan against an unknown image.
- Kubernetes CronJob: protect the pod-disruption budget when a per-pod scan could otherwise outlast the job's deadline.
- Exploratory scans: cap discovery against potentially-large container filesystems.
| Flag | Scope | Default |
|---|---|---|
--timeout <SECONDS> (this flag) |
Wall-clock cap on the entire waybill invocation |
Disabled |
waybill trace run --timeout <SECONDS> |
Caps the SUBPROCESS being traced (not waybill itself) | 0 (no timeout) |
| Internal per-fetch timeouts | OCI registry pulls, deps.dev HTTP requests, go mod graph subprocess |
Hardcoded defaults |
Whichever timeout fires first wins. The global --timeout is the only one
that brings waybill itself to a hard stop; the others are scoped to specific
operations.
Partial output may not be written when the watchdog fires — there are no
atomic-flush guarantees. Operators who need "produce-the-best-SBOM-you-can-
in-N-seconds" semantics should pair --timeout with --output to a specific
path and check for that file's presence (and validity) after the run:
waybill --timeout 600 sbom scan --path . --output project.cdx.json
case $? in
0) echo "scan completed within the time limit" ;;
124) echo "scan exceeded the time limit; partial output may not have been written" ;;
*) echo "scan failed with another error: $?" ;;
esacSkip directory subtrees matching the supplied path or glob pattern when
scanning. Repeatable for multiple entries; an environment-variable form
WAYBILL_EXCLUDE_PATH accepts a list joined by the platform's path-list
separator (: on Unix, ; on Windows). CLI flags and env-var entries
combine by union.
Some repositories carry throwaway fixture or sample projects in
directories like tests/fixtures/, examples/sample-projects/, or
services/*/testdata/. Out of the box, waybill walks into those subtrees
and emits their manifests as if they were real components — sometimes
also recording synthetic dependency edges declared in fixture manifests.
The Go ecosystem skips testdata/ and _-prefixed directories
unconditionally per go help packages;
every other ecosystem (cargo, maven, gem, pip, npm, gradle, nuget, yocto)
has no documented convention to lean on, so the skip is opt-in via this
flag.
Each entry is classified at parse time:
- Literal path — no glob metacharacters (
*,?,[). Interpreted as a path relative to the scan root.--exclude-path tests/fixturesmatches<root>/tests/fixturesand every directory underneath it, but NOTservices/a/tests/fixtures. - Pattern — contains at least one
*,?, or[. Compiled as a glob; matches directory paths at arbitrary depth.--exclude-path '**/testdata'matchestestdata,services/a/testdata, andapps/web/internal/testdatain one entry. Shell-quote the value so your shell doesn't expand*itself.
Single literal exclusion at repo root:
waybill sbom scan --path /path/to/repo --exclude-path tests/fixtures \
--format cyclonedx-json --output sbom.cdx.jsonTwo repeated literal paths:
waybill sbom scan --path /path/to/repo \
--exclude-path tests/fixtures \
--exclude-path examples/sample-projects \
--format cyclonedx-json --output sbom.cdx.jsonGlob pattern across a monorepo:
waybill sbom scan --path /path/to/monorepo --exclude-path '**/testdata' \
--format cyclonedx-json --output sbom.cdx.jsonPersistent exclusion list via env var (Unix):
export WAYBILL_EXCLUDE_PATH='tests/fixtures:**/testdata'
waybill sbom scan --path . --format cyclonedx-json --output sbom.cdx.jsonWhen at least one exclusion entry is in effect, the emitted SBOM carries
an envelope-level waybill:exclude-path annotation listing the active
entries verbatim (Constitution Principle X — Transparency). Consumers
can detect this from the CDX metadata.properties[] entry, the SPDX 2.3
creationInfo.annotations[] entry, or the SPDX 3 document-level
Annotation element. When the flag is omitted the annotation is absent
and the SBOM is byte-identical to a pre-feature waybill build against
the same inputs.
User-supplied entries are additive on top of the scanner's built-in skip set:
- Always skipped (no opt-out):
vendor/,node_modules/,bower_components/,target/,dist/,build/,out/,coverage/,__pycache__/,venv/,.-prefixed dirs, Go module-cache trees (go/pkg/mod/). - Go-specific (always skipped, documented convention):
testdata/,_-prefixed dirs. - User-supplied via
--exclude-path: added to all of the above.
You cannot use --exclude-path to re-enable scanning of a built-in skip.
| Symptom | Cause / fix |
|---|---|
error: --exclude-path entry <X>: <details> |
Pattern has unbalanced brackets or invalid glob syntax. Quote the value and check */?/[ placements. |
error: --exclude-path entry was empty |
An empty string was passed. Remove the empty entry. |
| Fixture component still appears | Pattern didn't match. Re-run with RUST_LOG=debug and look for exclude-path: skipping directory matched by user-supplied entry lines. If none appear, your pattern doesn't match the actual path; try --exclude-path '**/<dir-name>'. |
| Real component disappeared | Your exclusion is too broad. Use a more specific path or pattern. |
Merge an operator-supplied CDX 1.6 JSON document into the emitted SBOM, declaring ground truth the scanner cannot observe. Single-occurrence in v0.1; CDX 1.4 / 1.5 files are also accepted (the fields waybill reads are unchanged across versions).
waybill is an evidence-extracting scanner: it only emits components for artifacts it can observe on disk (manifests, lockfiles, installed packages, binary fingerprints). Three common cases leave the scanner blind to dependencies the operator knows about:
- SaaS dependencies — Stripe, Twilio, Datadog, Auth0. There's nothing on disk to inspect.
- Vendored libraries with no manifest — a copy of
liberror/dropped intothird_party/with noCargo.toml,package.json, or equivalent. The scanner walks the directory but finds nothing it recognizes. - Metadata gaps on otherwise-known components — the scanner found a component but its license, supplier, or copyright is empty because the upstream manifest omitted them. The operator knows the correct value from upstream documentation.
--supplement-cdx <PATH> is the operator's hook to fill those gaps
without modifying the scanner's evidence model.
The supplement is a small CDX 1.6 JSON document. At minimum:
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"components": [],
"services": [],
"dependencies": []
}Worked example: a Rust project that uses Stripe (SaaS) plus a vendored
liberror:
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"components": [
{
"type": "library",
"bom-ref": "liberror-1.2.3",
"purl": "pkg:generic/liberror@1.2.3",
"name": "liberror",
"supplier": { "name": "Acme Open Source Foundation" },
"licenses": [ { "license": { "id": "MIT" } } ],
"copyright": "© 2026 Acme"
}
],
"services": [
{
"bom-ref": "stripe-saas",
"name": "Stripe",
"provider": { "name": "Stripe, Inc." },
"endpoints": [ "https://api.stripe.com" ]
}
],
"dependencies": [
{
"ref": "pkg:cargo/my-app@1.0.0",
"dependsOn": [ "liberror-1.2.3", "stripe-saas" ]
}
]
}Invoke the scan:
waybill sbom scan --path . \
--supplement-cdx supplement.cdx.json \
--format cyclonedx-json --output sbom.cdx.jsonThe emitted SBOM contains every scanner-discovered component (the
Cargo project + its transitive deps) PLUS liberror as a declared
component PLUS Stripe under services[]. The dependencies[] block
carries the new edges from my-app to both supplement entries.
Per-component (supplement → emitted SBOM):
purl(required — the join key for collision-vs-additive)bom-ref(re-anchored at merge time to canonical PURLs where matches exist)name,version,supplier.namelicenses[],copyright,descriptionexternalReferences[](all types)hashes[],cpe
Per-service:
name(required)bom-ref,provider.name,endpoints[],descriptionlicenses[],externalReferences[]
Per-dependency edge:
ref(bom-ref OR canonical PURL — the source side)dependsOn[](bom-refs OR PURLs — the target side(s))
Other CDX fields (evidence, pedigree, swid, signature,
vulnerabilities, formulation, metadata.component, …) are
silently ignored. The supplement's metadata.component is always
ignored: --scan-as continues to own scan-target identity.
When a supplement PURL matches a scanner-discovered PURL, fields partition into two sets:
- Scanner wins (bytes-derived facts the developer can't gainsay):
hashes[],cpe, canonicalpurl,version, binary role. - Developer wins (operator-domain metadata the scanner can only
guess at):
licenses[],concluded_licenses[],supplier,copyright,name(display),description,externalReferences[](all types). - Catch-all default: scanner wins.
Each disagreement is recorded as a waybill:assertion-conflict
annotation on the merged component. Repeatable conflicts on one
component accumulate into a single property whose JSON-encoded value
is an ARRAY of conflict records:
{
"name": "waybill:assertion-conflict",
"value": "[{\"field\":\"licenses\",\"scanner_value\":[],\"supplement_value\":[{\"license\":{\"id\":\"Apache-2.0\"}}],\"winner\":\"supplement\",\"justification\":\"developer-metadata-override\"}]"
}The justification field is a 2-value enum derived mechanically from
the partition: developer-metadata-override when the operator won;
bytes-evident-detection-preserved when the scanner won.
A supplement entry asserting "no openssl" (via any mechanism — a contradicting fact, a confidence override, an explicit removal directive) does NOT remove the openssl component from the emitted SBOM. If the scanner fingerprinted openssl, openssl ships.
The merge can only ADD components (solo path) or REPLACE-IN-PLACE (collision path); it can never REMOVE. This is FR-015, enforced by a post-condition assertion in the merge code. The supplement is enrichment, not a discovery substitute.
Every supplement-introduced solo component carries
waybill:source-tier = "declared" so consumers can distinguish
operator-declared entries from scanner-observed ones at a glance.
Scanner-observed entries keep their existing tier value
(installed / analyzed / source). Collisions don't add the
declared value (the component is fundamentally a scanner discovery;
the supplement provided enrichment, not the original observation).
When --supplement-cdx <PATH> is in effect, the emitted SBOM carries
a document-scope waybill:supplement-cdx annotation on
metadata.properties[] with the value <path>@sha256:<hex> —
recording the operator's verbatim path argument plus a SHA-256 over
the supplement file's raw bytes. Consumers can verify the supplement
hasn't drifted from what fed the merge by recomputing the hash. When
the flag is omitted, the annotation is absent and the SBOM is
byte-identical to a pre-feature waybill build.
Parse / I/O / schema-validation failures cause non-zero exit before any walker begins. No partial SBOM is ever emitted on supplement failure. Error messages name the supplement path verbatim so operators can diagnose without re-running with debug logs.
| Symptom | Cause / fix |
|---|---|
error: supplement file '<path>' unreadable: ... |
Path doesn't exist or permissions denied. Check the path argument. |
error: supplement file '<path>' is not valid JSON: ... |
File has a syntax error. Run jq . <path> to find the bad line. |
error: supplement file '<path>' failed structural validation: ... |
The error message names the specific field: wrong bomFormat, unsupported specVersion, missing required purl, unparsable PURL, etc. |
error: supplement file declares duplicate PURL '<purl>' across components[] / services[] |
The same canonical PURL appears twice. Remove the duplicate. |
error: supplement file dependencies[] references unknown bom-ref or PURL '<ref>' |
A dependsOn entry doesn't match any supplement-internal bom-ref or any scanner-discovered PURL. Fix the reference or add the missing entry. |
| Operator-declared license doesn't appear on a scanner-discovered component | Currently the supplement license override flows through waybill:supplement-licenses annotation rather than overwriting the typed licenses[] field on Cargo main-module components emitted via metadata.component. End-to-end propagation onto metadata.component is tracked for milestone-119-phase-2. |
services[] section appears but operator wanted SPDX output |
v0.1 emits services natively in CDX 1.6 only. SPDX 2.3 / SPDX 3 service projection is deferred to milestone-119-phase-2. |
Disable Go package-level build-graph classification (milestone 112).
By default, when a Go source scan finds a go toolchain on PATH, waybill
runs go mod why -m -vendor against each main module (modules batched in
chunks of 20, 60-second total budget shared across the scan) to classify
go.sum-fallback modules that sit outside the build graph:
- not needed → emitted with CDX
scope: "excluded",waybill:build-inclusion: not-needed, andwaybill:build-inclusion-derivation: go-mod-why; - test-only → emitted with test lifecycle scope and
waybill:lifecycle-scope-derivation: go-mod-why; - needed by any main module → emitted unchanged.
With this flag set, the subprocess never runs and affected modules keep
the conservative always-on waybill:build-inclusion: unknown marker.
# Disable via flag
waybill sbom scan --path . --output project.cdx.json --no-go-mod-why
# Disable via env var (any non-empty value other than "0")
WAYBILL_NO_GO_MOD_WHY=1 waybill sbom scan --path . --output project.cdx.jsonClassification degrades rather than failing: a missing toolchain, a
failed go list all preflight, a non-zero go mod why exit, or budget
exhaustion all fall back to the unknown marker with a warning and a
one-line go-mod-why classification: … summary on stderr. See the
golang ecosystem notes for the full degrade
matrix. With --offline, the go children are pinned to GOPROXY=off,
GOFLAGS=-mod=mod, GOTOOLCHAIN=local.
Opt in to the milestone-235 Gradle transitive dependency resolver.
Without this flag, waybill's Gradle support is milestone-106's
lockfile reader only: gradle.lockfile +
buildscript-gradle.lockfile produce flat resolved components with
no transitive-edge information. Gradle projects WITHOUT a lockfile
produce zero components.
With --gradle-resolve set, waybill discovers each Gradle project
directory (any dir containing build.gradle(.kts) or
settings.gradle(.kts)), locates the ./gradlew wrapper (or a
gradle on $PATH), and spawns ./gradlew :<sub>:dependencies --configuration <config> --no-daemon per subproject × configuration.
The ASCII-tree output is parsed into transitive dependency edges.
Default configurations resolved: runtimeClasspath +
testRuntimeClasspath — extendable via
--gradle-extra-configurations.
Requires a JDK on $PATH. If the wrapper isn't found, gradle
isn't on $PATH, or the subprocess times out (default 5 min per
call), waybill falls back to the milestone-106 lockfile reader
(when a lockfile is present) or emits nothing for that project
(when it's not). No silent failure — the future milestone-235 US4
waybill:gradle-resolution-tier annotation will surface the
outcome per-scan.
waybill sbom scan --path ./my-gradle-project \
--gradle-resolve \
--output my-project.cdx.jsonThe other four --gradle-* flags (below) are inert without
--gradle-resolve.
By default, waybill passes --no-daemon to every ./gradlew
invocation so it doesn't leave a Gradle JVM sitting in the
operator's process list after a scan. Each subprocess call pays
the ~20–30s JVM cold-start cost.
--gradle-daemon opts out of --no-daemon. Subprocess calls
after the first become significantly faster (the daemon reuses
the same JVM), but a Gradle daemon may keep running after the
scan exits. Useful for iterative local scanning; not recommended
for CI.
Requires --gradle-resolve.
Also resolves the buildscript classpath (Gradle plugins the build
itself uses) via ./gradlew :<sub>:buildEnvironment. Doubles the
subprocess-call count. Default off; the buildscript classpath is
functionally distinct from the runtime classpath — it doesn't
ship in the artifact.
The pre-existing milestone-106 buildscript-gradle.lockfile
reader remains the primary path for buildscript coverage when a
lockfile is present (FR-009 non-regression). This flag adds
subprocess-tier coverage for projects without buildscript
lockfiles.
Requires --gradle-resolve.
Per-subprocess timeout for each ./gradlew :sub:dependencies
invocation. Default: 300 (5 minutes). Minimum: 1.
When a subprocess exceeds the timeout, waybill sends SIGTERM, waits 2 seconds, then sends SIGKILL. The project's resolution degrades to the next ladder tier (m106 lockfile if present; otherwise no components for that project). Larger values are appropriate when scanning a project whose Gradle wrapper points at a distribution that isn't yet downloaded locally (first invocation stalls on Gradle distribution download):
waybill sbom scan --path ./stale-wrapper-project \
--gradle-resolve \
--gradle-timeout-secs 900 \
--output project.cdx.jsonExtend the default runtimeClasspath + testRuntimeClasspath
configuration set with additional Gradle configurations. Repeatable:
waybill sbom scan --path ./my-gradle-project \
--gradle-resolve \
--gradle-extra-configurations compileClasspath \
--gradle-extra-configurations testCompileClasspath \
--output my-project.cdx.jsonTotal subprocess invocations = (subprojects) × (default configs + extras). Each configuration adds ~3–5s of daemon-hot scan time.
Configuration names are validated to reject shell metacharacters
(space, ;, backtick, $, |, &, >, <, newlines) so
attacker-controlled values can't reach the shell. Empty values
are rejected.
Requires --gradle-resolve.
Walk a directory or extracted container image and produce one or more SBOM formats from the package artifacts on disk. No eBPF required — runs anywhere Rust runs.
Exactly one of --path or --image is required.
| Flag | Type | Default | Description |
|---|---|---|---|
--path <PATH> |
path | (required if no --image) |
Directory to walk recursively. |
--image <IMAGE> |
path-or-OCI-ref | (required if no --path) |
Tarball path or OCI reference. |
--image-src <SRC[,SRC...]> |
enum (docker, remote) |
docker,remote |
Image source-resolution order. |
--image-platform <linux/ARCH[/VARIANT]> |
string | host arch | Multi-arch image platform pick. |
--no-oci-cache |
bool | off | Disable the OCI blob cache for registry pulls. |
--oci-cache-size <BYTES> |
u64 | 10737418240 (10 GB) |
Cap for the on-disk OCI blob cache. |
--registry-credentials-dir <PATH> |
path | (unset) | Directory containing Docker-format registry credentials (issue #235; for in-cluster operation). |
--output <[FMT=]PATH> |
string (repeatable) | per-format default | Output path override. |
--format <FORMAT> |
enum (repeatable, comma-separated) | cyclonedx-json |
Output format(s). |
--max-file-size <BYTES> |
u64 | 268435456 (256 MB) |
Skip files larger than this. |
--no-hashes |
bool | off | Omit per-component content hashes. |
--deb-codename <VALUE> |
string | auto-detect | Override distro= qualifier on deb PURLs. |
--no-package-db |
bool | off | Skip installed-package DB reads (dpkg/apk). |
--include-vendored |
bool | off | Emit CMake add_subdirectory(third_party/|vendor/...) entries. |
--no-deep-hash |
bool | off | Skip per-file SHA-256 of installed-package contents. |
--json |
bool | off | Print a JSON summary to stdout. |
--no-clearly-defined |
bool | off | Skip ClearlyDefined enrichment. |
--no-deps-dev |
bool | off | Skip deps.dev license enrichment. |
--no-deps-dev-graph |
bool | off | Skip deps.dev transitive dep-graph enrichment. |
--enrich-sources <SRC[,SRC...]> |
enum (deps-dev, clearly-defined, deps-dev-graph) |
(all enabled) | Allowlist of enrichment sources. |
--bind-to-source <PATH> |
path | (none) | Source-tier SBOM to bind emitted components against. |
--repo <URL> |
URL | auto-detect | Attach a repo: identifier. |
--git-ref <REVISION> |
string | auto-detect | Pair with --repo to upgrade to git:<repo>#<ref>. |
--image-id <REF> |
string | auto-detect | Attach an image: identifier. |
--attestation <IRI> |
IRI | (none) | Attach an attestation: identifier. |
--id <SCHEME=VALUE> |
string (repeatable) | (none) | Attach a user-defined identifier. |
--keep-credentials-in-identifiers |
bool | off | Preserve userinfo in auto-detected git URLs. |
--subject-hash <ALGO:HEX> |
string (repeatable) | (none) | Attach a subject: content-hash identifier. |
--component-id <PURL=SCHEME:VALUE> |
string (repeatable) | (none) | Attach a user-defined identifier to a specific component. |
--root-name <NAME> |
string | auto-derived | Override metadata.component.name. |
--root-version <VERSION> |
string | auto-derived | Override metadata.component.version. |
--root-purl-type <TYPE> |
string | (none) | Override the type segment of the root PURL. Defaults to generic when --root-name is set; this flag replaces that default. REQUIRES --root-name. Mutually exclusive with --no-root-purl. See --root-purl-type. |
--no-root-purl |
bool | off | Omit the root component's PURL entirely from the emitted SBOM. REQUIRES --root-name. Mutually exclusive with --root-purl-type. See --no-root-purl. |
--preserve-manifest-main-module |
bool | off | When set together with --root-name / --root-version / --root-purl, preserve the manifest-derived main-module identity as a library-typed entry in components[] rather than dropping it per the milestone-077 clean-replacement default. The demoted entry carries a waybill:demoted-from-main-module = "true" annotation per Constitution V parity-bridging audit (C102 row in docs/reference/sbom-format-mapping.md). No-op without an active root-override flag (silent + INFO log) and on multi-main-module scans (silent + INFO log). Milestone 149 / issue #151. |
--creator <TYPE: NAME> |
string (repeatable) | (none) | Attach a creator entry to the SBOM. |
--annotator <TYPE: NAME> |
string (repeatable, paired) | (none) | Document-level annotator. Pair 1:1 with --annotation-comment. |
--annotation-comment <TEXT> |
string (repeatable, paired) | (none) | Comment that pairs positionally with the preceding --annotator. |
--metadata-comment <TEXT> |
string | (none) | Free-text comment about the SBOM document. |
--scan-target-name <NAME> |
string | auto-derived | Operator override for the document/SBOM name field. |
--metadata-file <PATH> |
path | (none) | JSON sidecar with user-supplied metadata. |
--sbom-type <TYPE> |
enum (design/source/build/analyzed/deployed/runtime) |
auto-detect | Operator-asserted CISA SBOM Type. |
--spdx2-relationship-compat <PROFILE> |
enum (full/basic) |
full |
SPDX 2.3 relationship-vocabulary compatibility for scoped deps (issue #228). |
Directory to walk recursively for package artifacts. Files with recognised
package-artifact suffixes (.deb, .crate, .whl, .tar.gz, .jar, .gem,
.apk, …) are stream-hashed and matched against the path resolver.
waybill sbom scan --path . --output project.cdx.jsonContainer image to scan. Two accepted forms:
- A
docker save-format tarball path on disk. Layers are extracted into a tempdir (whiteouts honoured), then the resulting rootfs is scanned exactly like--path. - An OCI image reference (e.g.,
alpine:3.19orgcr.io/foo/bar@sha256:...). waybill auto-detects which based on whether the path exists on disk.
waybill sbom scan --image alpine.tar --output alpine.cdx.json
waybill sbom scan --image alpine:3.19 --output alpine.cdx.jsonFor OCI references waybill checks the local docker daemon's cache first, then
falls back to a registry pull on miss (matches docker run semantics).
Override the source-resolution order with --image-src.
Image source-resolution order. Comma-separated; waybill tries each source in
order and stops at the first one that has the image. Default docker,remote
matches trivy's --image-src and syft's auto-detection.
Possible values:
docker— local docker daemon (shells out todocker image inspectthendocker save).remote— OCI distribution-spec registry pull.
waybill sbom scan --image alpine:3.19 --image-src remote --output alpine.cdx.json--image-src remote forces a fresh registry fetch; --image-src docker fails
rather than touching the network. Ignored when --image resolves to a
tarball file on disk.
Override the platform that's resolved from a multi-arch image index. Only
meaningful when --image points at a registry reference. Format
<os>/<arch> or <os>/<arch>/<variant>. Only linux is supported as the OS.
Common values: linux/amd64, linux/arm64, linux/arm/v7, linux/386,
linux/ppc64le, linux/s390x. When omitted, auto-resolves to
linux/<host-arch>.
waybill sbom scan --image alpine:3.19 --image-platform linux/arm64 --output alpine-arm64.cdx.jsonUse case: a macOS arm64 dev machine scanning a linux/amd64 container deployed
to AWS, or Linux x86_64 CI scanning an arm64 image deployed to Graviton.
Disable the OCI blob cache for registry pulls. Equivalent to
WAYBILL_OCI_CACHE=0. When set, every blob (config + layer) is fetched fresh
on every scan, even if the same digest is already cached. Cache files on disk
are untouched.
Use case: CI lanes that want pure one-shot semantics, or debugging a registry-side regression.
Cap (in bytes) for the on-disk OCI blob cache. When the cache exceeds this
size, oldest-mtime entries are evicted until the total drops below the cap.
Default 10 GB. Equivalent env var WAYBILL_OCI_CACHE_SIZE.
Directory containing Docker-format registry credentials. Probes the K8s
secret-mount filenames in order: config.json (plain Docker convention),
.dockerconfigjson (K8s kubernetes.io/dockerconfigjson secret type),
.dockercfg (legacy K8s kubernetes.io/dockercfg secret type). First
readable + parseable file wins. The file format is the standard Docker
config.json shape (auths, credsStore, credHelpers); the existing
credential-resolution precedence applies inside the loaded config.
Use this when running waybill in a container that mounts a K8s
imagePullSecrets-derived volume (typically at
/var/run/secrets/registry/). For local/CI use with the standard Docker
keychain, leave this unset — waybill falls back to
$DOCKER_CONFIG/config.json or $HOME/.docker/config.json (issue #235).
Full credential-resolution priority chain (highest to lowest):
- Per-registry env vars
WAYBILL_REGISTRY_<HOST>_USERNAME+WAYBILL_REGISTRY_<HOST>_PASSWORD, where<HOST>is the registry hostname normalized to uppercase with[^A-Z0-9]replaced by_(e.g.ghcr.io→WAYBILL_REGISTRY_GHCR_IO_USERNAME). - Generic env vars
WAYBILL_REGISTRY_USERNAME+WAYBILL_REGISTRY_PASSWORD(applies to every registry). - The
--registry-credentials-dirpath described above. $DOCKER_CONFIG/config.jsonor$HOME/.docker/config.json(legacy/default behavior, unchanged).
If every source fails, waybill falls through to anonymous registry access — which works for public registries hosting public images.
# In-cluster CronJob pattern: K8s mounts an imagePullSecret-derived
# volume; waybill reads creds from there.
waybill sbom scan \
--image my-ecr.amazonaws.com/app:v1 \
--image-src remote \
--registry-credentials-dir /var/run/secrets/registryOutput path override. Two forms accepted:
- Bare
--output <path>— applies to the single requested format. Rejected when more than one format is requested. - Per-format
--output <fmt>=<path>— repeatable; each entry overrides the default filename for exactly one format id.
waybill sbom scan --path . \
--format cyclonedx-json,spdx-2.3-json \
--output cyclonedx-json=out.cdx.json \
--output spdx-2.3-json=out.spdx.jsonWhen omitted, each format writes to its own default filename
(waybill.cdx.json, waybill.spdx.json, waybill.spdx3.json).
Milestone 215. Emit one SBOM per detected workspace member (Cargo workspace
member, npm workspace member, Go workspace, Maven multi-module, pyproject dir,
gem sub-gem, etc.) instead of one combined SBOM. Requires --output-dir <DIR>;
incompatible with --output (a single file cannot hold N sub-SBOMs).
waybill sbom scan --path ./my-monorepo --split --output-dir ./sboms/
# Emits:
# ./sboms/<slug1>.<ecosystem>.cdx.json
# ./sboms/<slug2>.<ecosystem>.cdx.json
# …
# ./sboms/split-manifest.json ← operator-facing indexInteraction matrix:
--split |
--output <file> |
--output-dir <dir> |
Behavior |
|---|---|---|---|
| unset | any | any | Pre-feature single-SBOM behavior, unchanged. |
| set | any | any | HARD ERROR at CLI parse. |
| set | unset | set | Fan out: emit N × M sub-SBOMs + 1 manifest into <dir>. |
| set | unset | unset | HARD ERROR at CLI parse — pass --output-dir <dir>. |
Multi-format: passing --format multiple times produces N × M files
(N subprojects × M formats), grouped in the manifest's per-entry files map.
Zero-boundary fallback (FR-009): on a single-package project (no workspace
members detected), Waybill emits ONE SBOM identical to pre-feature output plus
a WARN log — the command still exits 0 so CI scripts opportunistically passing
--split don't break on non-monorepo trees.
Filename convention (per subproject × format):
<slug>.<ecosystem>.<format-ext>.json where <slug> derives from the
subproject's PURL name (lowercased, unsafe chars stripped), <ecosystem> is
the PURL type (cargo, npm, pypi, maven, go, gem, swift, …), and
<format-ext> is cdx / spdx / spdx3. Two subprojects that collide on
<slug>.<ecosystem> get an 8-hex-char SHA-256(source-dir) disambiguation
suffix; the result is deterministic across scans.
See split-manifest for the manifest schema + operator recipes for consuming the output.
Milestone 215. Directory that receives split-mode sub-SBOMs +
split-manifest.json. Required when --split is set; ignored otherwise.
Directory is created if missing.
Output format(s). Comma-separated and the flag itself is repeatable:
--format cyclonedx-json,spdx-2.3-json is equivalent to
--format cyclonedx-json --format spdx-2.3-json. Default cyclonedx-json.
Duplicates dedupe silently.
Registered formats:
cyclonedx-json— CycloneDX 1.6 JSON. Default filenamewaybill.cdx.json.spdx-2.3-json— SPDX 2.3 JSON. Default filenamewaybill.spdx.json.spdx-3-json— SPDX 3.0.1 JSON-LD. Default filenamewaybill.spdx3.json.spdx-3-json-experimental(deprecated alias) — byte-identical tospdx-3-json; emits a stderr deprecation notice. SetWAYBILL_NO_DEPRECATION_NOTICE=1to suppress.
See SBOM types for the per-format SBOM-type field positions and CISA SBOM Types vocab.
Maximum file size to hash, in bytes. Larger files are skipped. Default
268435456 (256 MB) covers the largest realistic package artifact.
Omit per-component content hashes from the SBOM. Reduces output size but disables byte-level tamper detection.
Optional distro codename to stamp on deb PURLs. Overrides the value
auto-derived from <root>/etc/os-release (ID + VERSION_ID →
distro=<id>-<version_id>). Useful when scanning a directory that isn't
itself a rootfs (e.g., a bare directory of .deb files).
waybill sbom scan --path ./debs --deb-codename debian-12 --output debs.cdx.jsonSkip reading installed-package databases (/var/lib/dpkg/status,
/lib/apk/db/installed). On by default because production container images
routinely clean up .deb/.apk artefact caches and the db is then the only
complete source. Pass this flag to fall back to pure artefact-file scanning.
Include vendored C/C++ dependencies declared via CMake
add_subdirectory(third_party/<name>) or add_subdirectory(vendor/<name>).
Default OFF — bare add_subdirectory(...) is also how CMake projects
include first-party src/ and tests/ sub-modules, so the gate requires
an explicit opt-in to avoid false positives. The path-prefix check rejects
anything that isn't under third_party/ or vendor/.
When enabled, waybill emits one pkg:generic/<name> component per
vendored entry with a waybill:vendored = true property. The version
segment is backfilled from a co-located version.txt or .version file
when present; otherwise the PURL has no version.
# Opt in via flag
waybill sbom scan --path . --output project.cdx.json --include-vendored
# Opt in via env var (accepts "1", "true", etc.)
WAYBILL_INCLUDE_VENDORED=1 waybill sbom scan --path . --output project.cdx.jsonSkip per-file SHA-256 hashing of installed-package contents. Falls back to a
fast SHA-256 over each package's dpkg .md5sums file (microseconds per
package; component-level identity only, no per-file occurrences).
Default-on hashing reads every file referenced by dpkg's .list manifest —
proportional to installed size (~3-5 s on debian:bookworm-slim, ~30 s on
full debian).
Print a JSON summary to stdout after writing the SBOM.
waybill sbom scan --path . --output project.cdx.json --jsonSkip ClearlyDefined enrichment (concluded licenses). Keeps deps.dev license +
dep-graph enrichment active. Use this when ClearlyDefined is slow or
unreachable but you still want deps.dev data. No effect when --offline is set.
Skip deps.dev license enrichment. Keeps ClearlyDefined and dep-graph enrichment active. The fastest enrichment source and rarely needs skipping.
Skip deps.dev transitive dep-graph enrichment. Keeps deps.dev license enrichment and ClearlyDefined active. Useful when the graph response is large or unneeded.
Comma-separated allowlist of enrichment sources to enable. When provided, ONLY
the listed sources run (overrides all --no-clearly-defined /
--no-deps-dev / --no-deps-dev-graph flags). No effect when --offline is
set.
Possible values:
deps-dev— deps.dev license enrichment (declared + observed licenses).clearly-defined— ClearlyDefined concluded-license enrichment.deps-dev-graph— deps.dev transitive dep-graph edge enrichment.
waybill sbom scan --path . --enrich-sources deps-dev,clearly-defined --output project.cdx.jsonPath to a source-tier SBOM document (CDX 1.6 / SPDX 2.3 / SPDX 3 JSON) that
emitted components will be bound to. When set, waybill emits a
waybill:source-document-binding annotation on each first-party component
whose PURL appears in the source SBOM, plus a document-level cross-document
reference.
Components whose PURL has no source-tier counterpart get an explicit
binding: unknown { reason: "source-not-found-in-bind-target" } marker.
waybill sbom scan --image my-image:latest --bind-to-source source.cdx.json --output image.cdx.json
waybill sbom verify-binding --image-sbom image.cdx.json --source-sbom source.cdx.jsonSee also: Cross-tier binding for the full algorithm and per-format carrier shapes.
Attach a repo: identifier — source repository identity (URL or git-style
ssh URL). Manual override; if both this flag and an auto-detected repo:
identifier (from .git/ origin remote) produce a value, manual wins. Pair
with --git-ref <revision> to upgrade to a git:<repo-url>#<revision>
identifier (the git: identifier supersedes — no separate repo: is also
emitted).
waybill sbom scan --path . --repo https://github.com/example/proj --git-ref v1.0.0 --output project.cdx.jsonSee also: Identifiers for the full identity model.
Pair with --repo <url> to emit a git:<repo>#<revision> identifier
(commit/branch/tag-anchored). Cannot be supplied without --repo. When set,
supersedes the bare repo: identifier — only the git: identifier is
emitted.
Attach an image: identifier — image identity in the form
[registry/]name[:tag][@sha256:digest]. Manual override: if --image <PATH>
(the scan input) is also set and auto-detection produced an image: value,
the manual value wins. Named --image-id to avoid colliding with the
--image <PATH> scan-input flag.
Attach an attestation: identifier — in-toto attestation IRI. Manual only;
no auto-detection equivalent.
Attach a user-defined identifier in <scheme>=<value> form. Repeatable. The
<scheme> MUST match regex ^[a-z][a-z0-9_-]*$ and MUST NOT collide with a
built-in scheme (repo, git, image, attestation) — use the dedicated
flags for those.
waybill sbom scan --path . \
--id acme_corp_id=svc-alpha-123 \
--id internal_ticket=PROJ-456 \
--output project.cdx.jsonUser-defined identifiers ride the waybill:identifiers document-level
annotation; SPDX 3 carries them natively in Element.externalIdentifier[].
See also: Identifiers for the full per-format carrier table and decode recipes.
Preserve userinfo (e.g., USER:TOKEN@host) in auto-detected git remote URLs
when constructing repo: and git: identifiers. By default, waybill strips
userinfo to prevent accidental credential disclosure in published SBOMs.
Use this flag only when the credentials are deliberately non-sensitive
(public read-only deploy token, internal-network-only credentials). Manual
--repo / --git-ref / --id flag values are emitted verbatim regardless
of this flag.
See also: Identifiers for the credential- stripping algorithm.
Attach a subject: identifier declaring "this SBOM describes the artifact
with the given content hash." Format: sha256:<64-lowercase-hex> or
sha512:<128-lowercase-hex>. Repeatable for multi-subject SBOMs.
waybill sbom scan --path . --subject-hash sha256:abc...def --output project.cdx.jsonOn build-tier scans (waybill trace run), subject identifiers are
auto-detected from the in-toto attestation envelope's subject set; manual
flags augment auto-detected entries (deduplicated by exact match). On
source-tier and image-tier scans, no auto-detect runs; manual flags are the
only source of subject: identifiers.
Attach a user-defined identifier to a specific component in the emitted SBOM.
The PURL must byte-equal a component's purl field; the SCHEME must be a
non-built-in scheme name (built-in schemes repo, git, image,
attestation, subject are reserved for document-level use). Repeatable.
waybill sbom scan --path . \
--component-id "pkg:cargo/serde@1.0.0=kusari-id:asset-shared-lib-v2" \
--component-id "pkg:cargo/myapp@0.5.1=acme-asset:myapp-prod-001" \
--output project.cdx.jsonIf a selector PURL matches multiple components (same PURL across different bom-ref values), the identifier is attached to ALL matching components. If a selector matches zero components, the scan logs a warning and continues.
See also: Identifiers for component-level identifier semantics.
Override the auto-derived metadata.component.name of the emitted SBOM.
Useful when scanning an arbitrary directory whose basename doesn't reflect
the operator-meaningful project identity. Accepts any non-empty UTF-8 except
whitespace, control characters, ?, and #. URL-encoded automatically when
emitted into the PURL name segment.
waybill sbom scan --path /tmp/extracted --root-name acme-platform --output platform.cdx.jsonWhen this flag is set on a manifest-driven scan (Cargo, npm, pip, gem, Maven, Go), the manifest-derived main-module component is dropped entirely from the emitted SBOM (clean replacement).
Override the auto-derived metadata.component.version. Same validation
rules as --root-name. Independent — can be set without --root-name and
vice versa. When unset, falls through to the auto-derived version
(typically 0.0.0 for arbitrary directories or the manifest-derived
version for project scans).
waybill sbom scan --path . --root-name acme-platform --root-version 2.4.1 --output platform.cdx.jsonOverride the type segment of the root component's PURL. By default,
when --root-name is supplied, waybill hardcodes the type to generic
(pkg:generic/<name>@<version>). This flag replaces that default so
the BOM subject's PURL can carry an operator-chosen ecosystem type.
Useful when a downstream consumer keys software identity on
(pkg_type, name) and an existing record was originally produced with
a different type (e.g., a Go build emitted by another tool as
pkg:golang/..., or a Maven build emitted as pkg:maven/...).
Matching the existing type on re-scan keeps the SBOM landing on the
existing identity row instead of spawning a new one.
REQUIRES --root-name to be supplied at the same time (an explicit
name accompanies the type-overriding output). Mutually exclusive with
--no-root-purl.
The type token is validated at parse time against the purl-spec
charset ^[a-z][a-z0-9.+-]*$ (lowercase ASCII alphanumeric plus
./+/-, starting with a letter). Invalid values produce a
clap-style error naming the flag.
waybill sbom scan --path . --root-name github.com/example/svc \
--root-version v1.2.3 --root-purl-type golang \
--output svc.cdx.json
# → metadata.component.purl = "pkg:golang/github.com%2Fexample%2Fsvc@v1.2.3"
waybill sbom scan --path . --root-name "@scope/pkg" --root-version 1.0.0 \
--root-purl-type npm --output pkg.cdx.json
# → metadata.component.purl = "pkg:npm/%40scope%2Fpkg@1.0.0"Applied identically across all three output formats: CycloneDX
metadata.component.purl, SPDX 2.3 root Package
externalRefs[purl], SPDX 3 root software_packageUrl +
externalIdentifier[packageUrl].
Omit the root component's PURL entirely from the emitted SBOM. The PURL slot is ABSENT (not null, not empty) in every format:
- CycloneDX:
metadata.component.purlfield is omitted. - SPDX 2.3: the root Package's
externalRefs[]contains no entry withreferenceType: "purl". The CPEexternalRefstill emits. - SPDX 3: the root
software_Packageelement has nosoftware_packageUrlfield AND itsexternalIdentifier[]contains no entry withexternalIdentifierType: "packageUrl". The CPE externalIdentifier entry still emits.
Useful when downstream consumers key software identity on
(pkg_type, name) and the target record was originally produced by a
tool that emitted no root PURL — reproducing that empty-type identity
requires omitting the PURL here.
REQUIRES --root-name to be supplied (an explicit name is the only
identity signal once the PURL is dropped). Mutually exclusive with
--root-purl-type.
waybill sbom scan --path . --root-name my-svc --no-root-purl \
--output svc.cdx.json
# → metadata.component.name = "my-svc"
# → metadata.component.version = "..." (from --root-version or default)
# → metadata.component.purl ABSENTThe name is taken from --root-name straight through to the
component-name slot without PURL encoding — slashes and @ in the
name round-trip byte-for-byte, e.g.:
waybill sbom scan --path . \
--root-name 767xxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/pico-server \
--root-version 1.0.0 --no-root-purl --output image.cdx.json
# → metadata.component.name preserves the full registry path verbatimMilestone 149 (closes issue #151).
When set together with --root-name / --root-version / --root-purl,
preserve the manifest-derived main-module identity as a library-typed
entry in components[] rather than dropping it per the milestone-077
clean-replacement default. The demoted entry carries a
waybill:demoted-from-main-module = "true" annotation per Constitution
Principle V parity-bridging audit (C102 row in
docs/reference/sbom-format-mapping.md).
Why use it: shipped services often have an internal Cargo / npm /
etc. crate name that differs from the operator-meaningful deployment
identity. --root-name widget-svc --root-version 1.2.3 cleanly replaces
the manifest identity at the root, but the compliance auditor loses
visibility into the original manifest PURL + license + hashes. Setting
this flag preserves both: the operator identity at the root AND the
manifest identity as a library sibling carrying its full ecosystem-
derived metadata.
Worked example (Cargo project with [package].name = "foo-internal",
version = "0.5.1"):
waybill sbom scan --path . \
--root-name widget-svc --root-version 1.2.3 \
--preserve-manifest-main-module \
--output svc.cdx.json
# → metadata.component = {name: "widget-svc", version: "1.2.3", type: "application"}
# → components[] contains:
# {
# "name": "foo-internal", "version": "0.5.1",
# "purl": "pkg:cargo/foo-internal@0.5.1",
# "type": "library",
# "properties": [
# { "name": "waybill:demoted-from-main-module", "value": "true" }
# ]
# }Edge cases:
- Without an active root-override flag: silent no-op with an INFO-level
diagnostic (
--preserve-manifest-main-module has no effect without --root-name override). The manifest-derived main-module continues to be the SBOM root per the existing milestone 053/064–070 behavior. - On multi-main-module scans (Cargo workspace, polyglot monorepo per milestone 127): silent no-op with an INFO-level diagnostic. The flag semantic requires a SINGLE manifest-derived main-module to demote; multi-module scans don't promote any to root, so there's nothing to demote. The override clean-replacement semantic stays unchanged.
- PURL collision (a transitive library dep happens to share the manifest-main-module PURL — rare): the existing deduplicator collapses pre-helper; the merged entry demotes cleanly carrying the demote annotation.
Dep-graph topology: per US1 clarification recorded 2026-06-29, the
demoted entry has NO outbound dependsOn edges in the wire output. Its
dep-graph topology is re-anchored on the operator-override root via the
existing milestone-084 logic. Queries like "what does the operator-
override root depend on?" return the full transitive graph; queries
against the demoted entry return zero outbound edges.
Wire format per output: applied identically across all three formats
(CycloneDX 1.6 components[] library entry, SPDX 2.3 Package element,
SPDX 3 software_Package element). The annotation VALUE is byte-
identical across formats per FR-009. The annotation SUBJECT in SPDX 3
routes to the synth-root IRI rather than the demoted entry's own IRI due
to package_iri_by_purl aliasing — consumers querying by annotation
field key (not by subject IRI) find the annotation regardless. See the
C102 row in docs/reference/sbom-format-mapping.md for the full
subject-routing note.
Backward compatibility: the flag is OPT-IN with default OFF. Operators
who use --root-name without setting this flag continue to get
milestone-077's clean-replacement output byte-for-byte (no demoted entry,
no annotation, no behavior change). The 4-flag root-override family
(--root-name, --root-version, --root-purl-type, --no-root-purl,
--root-purl) stays unchanged.
Attach a creator entry to the emitted SBOM. Repeatable. Form:
<Type>: <Name> where <Type> is one of Tool, Organization, Person
(case-sensitive).
Each entry lands at the standards-native creator/tools field of every emitted format:
- CDX 1.6
metadata.tools.components[]/metadata.manufacturer/metadata.authors[](per Type). - SPDX 2.3
creationInfo.creators[](verbatim). - SPDX 3
Tool/Organization/Personelement in@graph.
waybill's own auto-populated tool/organization entries are preserved alongside.
waybill sbom scan --path . \
--creator "Tool: trivy@0.50.0" \
--creator "Organization: Acme Corp" \
--creator "Person: alice@acme.example" \
--output project.cdx.jsonSee also: Identifiers for the per-format carrier table.
Attach a document-level annotator. Repeatable. MUST be paired 1:1 with
--annotation-comment — each --annotator MUST be immediately followed by
exactly one --annotation-comment. Form: same <Type>: <Name> shape as
--creator.
waybill sbom scan --path . \
--annotator "Person: bob@acme.example" \
--annotation-comment "Reviewed by security team 2026-04-15" \
--output project.cdx.jsonSee also: Identifiers for the annotation landing slots.
Free-text comment about the SBOM document as a whole. Single-valued. Lands
at SPDX 2.3 creationInfo.comment, SPDX 3 Annotation element of type
OTHER, CDX 1.6 bom.annotations[].
waybill sbom scan --path . --metadata-comment "Generated for SOC2 audit 2026-Q2" --output project.cdx.jsonOperator-supplied override for the document/SBOM-level name field. Lands at
SPDX 2.3 document name, SPDX 3 software_Sbom.name, CDX 1.6
metadata.component.name.
When both --scan-target-name and --root-name are set on a CDX emission,
--root-name takes precedence on metadata.component.name (a stderr
warning is emitted). On SPDX 2.3 / SPDX 3 the two flags target different
fields and both are honored independently.
Path to a JSON sidecar file containing user-supplied metadata. Schema:
{
"creators": ["Tool: trivy@0.50.0"],
"annotators": [{"type_name": "Person: bob@acme.example", "comment": "reviewed"}],
"metadata_comment": "Generated for SOC2 audit",
"scan_target_name": "acme-platform"
}deny_unknown_fields applies. Array fields merge additively with their flag
counterparts (file values come first); single-valued fields fail with a
conflict error if specified in both.
waybill sbom scan --path . --metadata-file metadata.json --output project.cdx.jsonOverride the auto-detected SBOM type with an operator-asserted CISA SBOM
Type. Valid values: design, source, build, analyzed, deployed,
runtime. Document-level only — per-component waybill:sbom-tier
annotations preserve auto-detected values.
When set, CDX metadata.lifecycles[], SPDX 2.3 creationInfo.comment
"Observed lifecycle phases", and SPDX 3
software_Sbom.software_sbomType[] all collapse to a single-element output
reflecting the asserted type.
waybill sbom scan --path . --sbom-type build --output build.cdx.jsonSee also: SBOM types for the four-column equivalence reference and per-format field positions.
Selects the SPDX 2.3 relationship-type vocabulary waybill uses for
scoped dependency edges (dev, build, test). Only affects the
spdx-2.3-json format — CDX and SPDX 3 emission are unaffected.
Both modes are spec-conformant, but they are not equivalent. The
SPDX 2.3 spec defines DEV_DEPENDENCY_OF, BUILD_DEPENDENCY_OF,
and TEST_DEPENDENCY_OF for exactly the purpose of expressing
dev/build/test scope on a dependency edge — the spec's intent is
clearly that you should use the most specific field that applies.
Constitution Principle X (Transparency) further requires waybill to
default to the spec-native mechanism that preserves the most
consumer-actionable signal. waybill defaults to full for both
reasons: we want more transparency in SBOM output, not less.
basic is provided as an explicit downshift for compatibility with
downstream tooling that doesn't implement the typed scoped variants.
Choose it deliberately, knowing you're trading spec-rich expression
for tool-compat reach.
Valid values:
full(default): emit the spec-native typed reversed-direction variantsDEV_DEPENDENCY_OF,BUILD_DEPENDENCY_OF,TEST_DEPENDENCY_OF. Every scoped edge carries its scope in the relationship type itself — the SPDX 2.3 spec's purpose-built field. Use this when emitting SBOMs for tooling that implements the full SPDX 2.3 relationshipType enum, and as the standing default for any output you intend to be maximally informative.basic: emit every dep, regardless of scope, as a natural- directionDEPENDS_ONedge. Use this when emitting SBOMs for downstream tooling that only implements the basic relationship vocabulary (e.g., Trivy, Syft, and tooling built on top of them). Such consumers would silently drop the typed scoped variants altogether, so collapsing scoped deps toDEPENDS_ONmakes the graph readable to them — at the cost of moving the dev/build/test signal off the edge and onto the Package annotation.
Crucially, the scope distinction is preserved in BOTH modes via the
waybill:lifecycle-scope annotation on the target Package (values
development / build / test; absent on runtime deps). This means
the dev/build/test signal is always recoverable from the document
itself — full puts it both on the edge and on the Package; basic
puts it on the Package only. This is consumer-critical signal:
vulnerability scanners, license auditors, and deployment-policy tools
need it to distinguish a CVE on a shipped component from one against
a test-only dep like testify or junit.
# Default — full SPDX 2.3 relationship vocabulary (typed scoped variants).
waybill sbom scan --path . --format spdx-2.3-json --output project.spdx.json
# Basic vocabulary only — every dep emits as natural-direction DEPENDS_ON.
waybill sbom scan --path . \
--spdx2-relationship-compat basic \
--format spdx-2.3-json \
--output project.spdx.jsonSee also: SBOM format mapping rows B2 + C42 for the full cross-format consumer story.
Verify a signed attestation (DSSE envelope) against a key, identity, or in-toto layout.
| Flag | Type | Default | Description |
|---|---|---|---|
--public-key <PEM> |
path | (none) | PEM public key (mutually exclusive with --identity). |
--identity <PATTERN> |
string | (none) | Keyless identity (email, URL, glob). |
--layout <PATH> |
path | (none) | Verify against an in-toto layout. |
--expected-subject <PATH> |
path (repeatable) | (none) | Verify on-disk SHA-256 matches a subject. |
--no-transparency-log |
bool | off | Tolerate keyless envelopes without Rekor proof. |
--fulcio-url <URL> |
URL | https://fulcio.sigstore.dev |
Custom Fulcio URL. |
--rekor-url <URL> |
URL | https://rekor.sigstore.dev |
Custom Rekor URL. |
--json |
bool | off | Emit a structured VerificationReport to stdout. |
Path to a signed .json / .dsse attestation file.
waybill sbom verify build.attestation.json --public-key signer.pubPEM-encoded public key expected to have signed the attestation. Mutually
exclusive with --identity.
Expected signer identity (email, URL, or glob) for keyless-signed attestations.
waybill sbom verify build.attestation.json --identity 'alice@acme.example'Verify against an in-toto layout. When omitted, only envelope-level checks run (signature + subject).
Verify the on-disk SHA-256 of <PATH> matches one of the attestation's
subjects. Repeatable for multi-subject envelopes.
waybill sbom verify build.attestation.json \
--public-key signer.pub \
--expected-subject ./my-binaryDon't require a Rekor inclusion proof in the envelope. Keyless-mode only.
Override the Fulcio certificate-issuance URL / Rekor transparency-log URL.
Useful for private sigstore instances. Defaults
https://fulcio.sigstore.dev and https://rekor.sigstore.dev.
Emit a structured verification report to stdout. Non-zero exit codes:
1 crypto failure, 2 envelope failure, 3 layout failure.
Add license, VEX, and supplier data to an existing SBOM, applying RFC 6902 JSON Patch ops with per-patch provenance recording.
| Flag | Type | Default | Description |
|---|---|---|---|
--patch <PATH> |
path (repeatable) | (none) | RFC 6902 JSON Patch file. |
--author <NAME> |
string | unknown |
Recorded author of the enrichment. |
--output <PATH> |
path | (overwrite input) | Output path. |
--base-attestation <PATH> |
path | (none) | Attestation the SBOM was derived from (SHA-256 embedded). |
--vex-overrides <PATH> |
path | (none) | OpenVEX 0.2.0 document with statements to propagate. |
--vex-propagation-mode <MODE> |
enum (permissive, caveated, strict) |
caveated |
Binding-aware VEX propagation mode. |
--skip-licenses |
bool | off | Skip license enrichment. |
--skip-supplier |
bool | off | Skip supplier enrichment. |
--skip-vex |
bool | off | Skip VEX enrichment. |
--deps-dev-timeout <MS> |
u64 | 5000 |
Timeout per deps.dev API call. |
--json |
bool | off | JSON summary to stdout. |
Path to the CycloneDX SBOM to enrich in place.
RFC 6902 JSON Patch file. Repeatable: patches are applied in order (later ops see earlier ones). At least one patch is required.
waybill sbom enrich project.cdx.json \
--patch licenses.patch.json \
--patch vex.patch.json \
--author "alice@acme.example"Recorded author of the enrichment. Defaults to unknown with a warning.
Lands in the per-patch provenance property.
Output path. Defaults to overwriting the input SBOM in place.
Optional path to the attestation the SBOM was derived from. Its SHA-256 gets embedded so verifiers can walk back to the attested source.
Path to a source-tier OpenVEX 0.2.0 document whose statements will be
propagated onto components in <SBOM_FILE>. Each propagation is gated by
the target component's waybill:source-document-binding strength per
--vex-propagation-mode.
VEX propagation mode. Possible values:
permissive— pre-072 behavior; propagate by PURL match without binding check.caveated(default) — propagate but tag binding-unverified statements withwaybill:vex-binding-status: unverified.strict— refuse propagation when binding strength != Verified (exit non-zero).
See also: Cross-tier binding for the binding strength definitions.
Skip the corresponding enrichment phase. Useful for narrow re-runs (e.g., only re-enrich VEX after a triage pass).
Timeout per deps.dev API call, in milliseconds. Default 5000.
Print a JSON summary to stdout.
Verify that an image-tier SBOM's per-component waybill:source-document-binding
annotations match the recompute against a source-tier SBOM. Exits non-zero on
any verification failure.
| Flag | Type | Default | Description |
|---|---|---|---|
--image-sbom <PATH> |
path (required) | — | Image-tier SBOM (CDX/SPDX 2.3/SPDX 3 JSON). |
--source-sbom <PATH> |
path (required) | — | Source-tier SBOM (JSON). |
--format <FORMAT> |
enum (table, json) |
table |
Output format. |
Path to the image-tier SBOM (JSON). Required.
Path to the source-tier SBOM (JSON). Required.
Output format. Possible values:
table(default) — plain-text per-row table.json—VerifyReportJSON for CI pipelines / machine consumption.
waybill sbom verify-binding \
--image-sbom image.cdx.json \
--source-sbom source.cdx.json \
--format jsonSee also: Cross-tier binding for the verification algorithm.
Trace an image-tier component back to its candidate source-tier SBOMs. For each instance of the supplied PURL in the image SBOM, reports the binding state against every candidate source SBOM. Always exits 0 (informational).
| Flag | Type | Default | Description |
|---|---|---|---|
--component-purl <PURL> |
string (required) | — | PURL of the component to trace. |
--image-sbom <PATH> |
path (required) | — | Image-tier SBOM. |
--source-sbom <PATH> |
path | (none) | Single candidate source-tier SBOM. |
--candidate-sources-dir <DIR> |
path | (none) | Directory of candidate source-tier SBOMs. |
--format <FORMAT> |
enum (table, json) |
table |
Output format. |
--source-sbom and --candidate-sources-dir are mutually exclusive.
PURL of the component to trace. Required.
Image-tier SBOM (CDX/SPDX 2.3/SPDX 3 JSON). Required.
Single candidate source-tier SBOM. Mutually exclusive with
--candidate-sources-dir.
Directory containing candidate source-tier SBOMs. Every *.cdx.json,
*.spdx.json, *.spdx3.json, or *.json file in the directory is loaded
and tested.
waybill sbom trace-binding \
--component-purl "pkg:cargo/serde@1.0.0" \
--image-sbom image.cdx.json \
--candidate-sources-dir ./source-sbomsOutput format. Possible values:
table(default) — plain-text per-row table.json—TraceReportJSON for CI pipelines / machine consumption.
See also: Cross-tier binding for the binding-state vocabulary.
Status: experimental. Linux-only. Adds ~2-3× wall-clock overhead on syscall-heavy builds; requires CAP_BPF + CAP_PERFMON; coverage gaps on
openat2andio_uring. Preferwaybill sbom scanunless you need a trace-bound attestation.
Capture a build trace and produce both an SBOM and an in-toto attestation in one step.
| Flag | Type | Default | Description |
|---|---|---|---|
<COMMAND>... |
positional (required) | — | Build command to trace (after --). |
--sbom-output <PATH> |
path | waybill.cdx.json |
SBOM output path. |
--attestation-output <PATH> |
path | waybill.attestation.json |
Attestation output path. |
--format <FORMAT> |
enum | cyclonedx-json |
SBOM output format. |
--no-enrich |
bool | off | Skip enrichment step. |
--include-source-files |
bool | off | Also include observed source files. |
--no-hashes |
bool | off | Omit per-component hashes. |
--trace-children |
bool | off | Follow forked children. |
--libssl-path <PATH> |
path | auto-detect | Override libssl.so path for uprobe attachment. |
--ring-buffer-size <BYTES> |
u32 | 8388608 |
BPF ring buffer size (must be power of two). |
--timeout <SECONDS> |
u64 | 0 (no timeout) |
Trace timeout. |
--skip-purl-validation |
bool | off | Skip online PURL existence validation. |
--lockfile <PATH> |
path | (none) | Lockfile for dependency-relationship enrichment. |
--artifact-dir <DIR> |
path (repeatable) | (none) | Post-trace artifact directories. |
--auto-dirs |
bool | off | Auto-detect artifact directories. |
--json |
bool | off | JSON summary to stdout. |
--repo <URL> |
URL | auto-detect (git remote) | Override the auto-detected repo: identifier. |
--git-ref <REVISION> |
string | auto-detect | Pair with --repo for git: identifier. |
--image-id <REF> |
string | (none) | Attach an image: identifier. |
--attestation <IRI> |
IRI | (none) | Attach an attestation: identifier. |
--id <SCHEME=VALUE> |
string (repeatable) | (none) | Attach a user-defined identifier. |
--keep-credentials-in-identifiers |
bool | off | Preserve userinfo in auto-detected URLs. |
--subject-hash <ALGO:HEX> |
string (repeatable) | auto-detect | Attach a subject: identifier. |
--component-id <PURL=SCHEME:VALUE> |
string (repeatable) | (none) | Component-level user identifier. |
--root-name <NAME> |
string | auto-derived | Override metadata.component.name. |
--root-version <VERSION> |
string | auto-derived | Override metadata.component.version. |
--creator <TYPE: NAME> |
string (repeatable) | (none) | Attach a creator entry. |
--annotator <TYPE: NAME> |
string (repeatable, paired) | (none) | Document-level annotator. |
--annotation-comment <TEXT> |
string (repeatable, paired) | (none) | Pairs with --annotator. |
--metadata-comment <TEXT> |
string | (none) | SBOM-level free-text comment. |
--scan-target-name <NAME> |
string | auto-derived | Operator override for document name. |
--metadata-file <PATH> |
path | (none) | JSON sidecar for user metadata. |
--sbom-type <TYPE> |
enum | auto-detect | Operator-asserted CISA SBOM Type. |
--signing-key <PATH> |
path | (none) | PEM private key for local-key signing. |
--signing-key-passphrase-env <NAME> |
env var name | (none) | Env var holding signing-key passphrase. |
--keyless |
bool | off | Keyless signing via OIDC → Fulcio → Rekor. |
--fulcio-url <URL> |
URL | https://fulcio.sigstore.dev |
Custom Fulcio URL. |
--rekor-url <URL> |
URL | https://rekor.sigstore.dev |
Custom Rekor URL. |
--no-transparency-log |
bool | off | Skip Rekor upload (keyless mode). |
--require-signing |
bool | off | Hard-fail if no signing identity is configured. |
--subject <PATH> |
path (repeatable) | auto-detect | Explicit subject artifact path. |
--attestation-format <FORMAT> |
enum (witness-v0.1, waybill-v1) |
witness-v0.1 |
Attestation output format. |
Build command to trace. Pass after -- to separate waybill flags from the
build command's flags.
waybill trace run --sbom-output build.cdx.json -- cargo install ripgrepSBOM output path. Default waybill.cdx.json.
Attestation output path. Default waybill.attestation.json.
SBOM output format. Default cyclonedx-json. See waybill sbom scan --format for the registered format set.
Skip the enrichment step entirely (no deps.dev / ClearlyDefined calls).
Also include observed source files (not just packages). Switches SBOM scope
from packages to source.
Follow forked children of the traced command. Useful when the build command spawns subprocesses (cargo → rustc, npm → node, etc.).
Override libssl.so path for uprobe attachment. Default: auto-detect from
the build's process environment.
BPF ring buffer size in bytes (must be a power of two). Default 8388608
(8 MB). Increase for high-syscall-rate builds where the buffer overflows.
Trace timeout in seconds. 0 means no timeout. Useful for unattended CI
runs that should not hang indefinitely.
Skip the online PURL existence validation step.
Path to a lockfile for dependency-relationship enrichment. Auto-detects
format (Cargo.lock, package-lock.json, go.sum). Unrecognised formats
are logged and skipped.
Post-trace artifact-directory scanning. See the waybill trace capture
section below for the full semantics — trace run forwards both flags
verbatim to capture.
--repo <URL>, --git-ref <REVISION>, --image-id <REF>, --attestation <IRI>, --id <SCHEME=VALUE>, --keep-credentials-in-identifiers, --subject-hash <ALGO:HEX>, --component-id <PURL=SCHEME:VALUE>, --root-name <NAME>, --root-version <VERSION>, --creator <TYPE: NAME>, --annotator <TYPE: NAME>, --annotation-comment <TEXT>, --metadata-comment <TEXT>, --scan-target-name <NAME>, --metadata-file <PATH>, --sbom-type <TYPE>
These flags share semantics with their waybill sbom scan counterparts —
see the waybill sbom scan section above for the full per-flag documentation.
Build-tier-specific notes:
--repoand--git-refauto-detect fromgit remote get-urlwhen the invocation cwd is a git checkout; the flag overrides the auto-detected value.--subject-hashaugments the auto-detected subject set from the in-toto attestation envelope (deduplicated by exact match).
See also: Identifiers for build-tier auto-detection semantics.
Path to a PEM-encoded private key for local-key DSSE signing. Mutually
exclusive with --keyless.
Env var name holding the passphrase for an encrypted --signing-key. No
effect on unencrypted keys. No interactive prompt — CI-friendly by design.
Keyless signing via OIDC → Fulcio → Rekor. Mutually exclusive with
--signing-key. Auto-detects GitHub Actions OIDC tokens.
Override the Fulcio / Rekor URLs. Defaults https://fulcio.sigstore.dev
and https://rekor.sigstore.dev.
Skip Rekor upload + inclusion-proof embedding. Keyless-mode only.
Fail if no signing identity was configured. Flips the default "emit unsigned + warn" behavior to a hard error.
Explicit subject artifact path. Repeatable. When set, auto-detection is suppressed — waybill signs exactly what you told it to.
Attestation output format. Possible values:
witness-v0.1(default) — in-toto Statement v0.1 wrapped around a witness attestation-collection (material+command-run+productnetwork-traceinner attestors). Directly consumable bysbomit generateand any go-witness-aware verifier.
waybill-v1— waybill's nativeBuildTracePredicateStatement v1. Richer network-trace semantics but only waybill understands it.
Status: experimental. Same caveats as
trace run.
Capture a build trace via eBPF and produce an in-toto attestation. Lower-level
than trace run — produces no SBOM, just the attestation.
Exactly one of --target-pid <PID> or a command after -- is required (mutually
exclusive).
| Flag | Type | Default | Description |
|---|---|---|---|
[COMMAND]... |
positional | — | Build command to trace (after --). |
--target-pid <PID> |
u32 | (none) | Existing PID to attach to. |
--output <PATH> |
path | waybill.attestation.json |
Attestation output path. |
--trace-children |
bool | off | Follow forked children. |
--libssl-path <PATH> |
path | auto-detect | Override libssl.so path. |
--go-binary <PATH> |
path | (none) | Go binary for Go-specific instrumentation. |
--ring-buffer-size <BYTES> |
u32 | 8388608 |
BPF ring buffer size (must be power of two). |
--timeout <SECONDS> |
u64 | 0 |
Trace timeout. |
--artifact-dir <DIR> |
path (repeatable) | (none) | Post-trace artifact directories. |
--auto-dirs |
bool | off | Auto-detect artifact directories. |
--json |
bool | off | JSON summary to stdout. |
--signing-key <PATH> |
path | (none) | PEM private key for local-key signing. |
--signing-key-passphrase-env <NAME> |
env var name | (none) | Env var holding signing-key passphrase. |
--keyless |
bool | off | Keyless signing via OIDC → Fulcio → Rekor. |
--fulcio-url <URL> |
URL | https://fulcio.sigstore.dev |
Custom Fulcio URL. |
--rekor-url <URL> |
URL | https://rekor.sigstore.dev |
Custom Rekor URL. |
--no-transparency-log |
bool | off | Skip Rekor upload (keyless mode). |
--require-signing |
bool | off | Hard-fail if no signing identity is configured. |
--subject <PATH> |
path (repeatable) | auto-detect | Explicit subject artifact path. |
--attestation-format <FORMAT> |
enum | witness-v0.1 |
Attestation output format. |
Existing process ID to attach the trace to. Mutually exclusive with the
-- <COMMAND> form.
Attestation output path. Default waybill.attestation.json.
Directories to scan for freshly-landed artifact files after the traced
command exits. Any recognised package file (.deb, .crate, .whl,
.tar.gz, …) whose mtime is newer than the trace start is hashed and
added to the file-access record. The resulting SBOM (when trace run
produces one) carries real content hashes even when the kernel-side
kprobe misses the output-file open. Repeatable or comma-separated.
Auto-detect artifact directories from the traced command. Matches
argv[0] against a table of known build tools (cargo, pip, npm, go,
apt-get, …) and merges the canonical cache paths with any explicit
--artifact-dir values. Skipped for shell-wrapped commands
(bash -c "…") — those are too dynamic to introspect.
Path to a Go binary for Go-specific instrumentation. Used to attach
uprobes to the Go runtime's TLS internals (Go binaries don't link
libssl.so so the standard --libssl-path path doesn't apply).
--trace-children, --libssl-path, --ring-buffer-size, --timeout,
--json, --signing-key, --signing-key-passphrase-env, --keyless,
--fulcio-url, --rekor-url, --no-transparency-log, --require-signing,
--subject, --attestation-format — see the waybill trace run section
above; identical semantics.
Generate a starter in-toto layout bound to a functionary key. Use the emitted
layout with waybill sbom verify --layout to enforce functionary + step-name
policy on signed attestations. Layouts are standard in-toto — any
in-toto-aware verifier accepts them.
| Flag | Type | Default | Description |
|---|---|---|---|
--functionary-key <PATH> |
path (required) | — | PEM-encoded public key of the expected signer. |
--output <PATH> |
path | layout.json |
Output path. |
--step-name <NAME> |
string | build-trace-capture |
Name of the layout's single step. |
--expires <DURATION> |
duration | 1y |
Validity window. Accepts 1y, 6m, 18mo, 2y, 30d, 52w. |
--readme <TEXT> |
string | (none) | Embedded human-readable description. |
PEM-encoded public key of the expected signer. Required.
waybill policy init --functionary-key signer.pub --output layout.jsonWhere to write the layout. Default layout.json.
Name of the single step the layout expects. Default build-trace-capture.
How long the layout is valid. Default 1y. Accepted suffixes: y (years),
m/mo (months), d (days), w (weeks).
Optional human-readable description embedded in the layout.
The --format flag accepts a comma-separated list and is itself repeatable.
Default is cyclonedx-json. Duplicates dedupe silently.
| Format id | Status | Default filename |
|---|---|---|
cyclonedx-json |
Stable. Default. CycloneDX 1.6 JSON. | waybill.cdx.json |
spdx-2.3-json |
Stable. SPDX 2.3 JSON. Validates against the official SPDX 2.3 JSON schema. | waybill.spdx.json |
spdx-3-json |
Stable. SPDX 3.0.1 JSON-LD. Production-grade output with native-field + annotation parity vs. CDX and SPDX 2.3. | waybill.spdx3.json |
spdx-3-json-experimental (deprecated) |
Byte-identical to spdx-3-json; emits a stderr deprecation notice. |
waybill.spdx3-experimental.json |
OpenVEX sidecar — when the scan produces VEX statements AND SPDX 2.3
output is requested, waybill co-emits an OpenVEX 0.2.0 JSON file alongside
the SPDX file. The SPDX document carries a DocumentRef-OpenVEX entry in
externalDocumentRefs with a SHA-256 of the sidecar bytes. Use
--output openvex=<path> to retarget the sidecar.
See Generation for the per-format builder architecture and SBOM format mapping for the cross-format data-placement map.
When --image <ref> resolves to an OCI reference (rather than a tarball),
waybill uses the same Docker keychain that docker pull uses —
~/.docker/config.json (or $DOCKER_CONFIG/config.json if set). No
waybill-specific credential file or CLI flag is required.
Credentials resolve in this priority order, matching Docker's:
credHelpers.<registry>— per-registry helper override (AWS ECR viadocker-credential-ecr-login, Google Artifact Registry viadocker-credential-gcloud).credsStore— registry-wide helper (osxkeychain,wincred,secretservice,pass,desktop).auths.<registry>.auth— direct credentials, base64-encoded asuser:password.auths.<registry>.identitytoken— registry-issued bearer token.
Example ~/.docker/config.json shapes:
{
"auths": {
"ghcr.io": { "auth": "<base64('username:ghp_xxx')>" }
}
}{
"auths": { "ghcr.io": {} },
"credsStore": "desktop"
}{
"credHelpers": {
"123456789012.dkr.ecr.us-east-1.amazonaws.com": "ecr-login"
}
}Behavior notes:
- Anonymous fallback when no entry matches.
- Helper failure (non-zero exit, "credentials not found" sentinel) falls through to anonymous so unrelated public-image scans aren't blocked.
- Credentials are never logged at any verbosity level.
- AWS ECR token TTL is 12 hours — never matters for a one-shot CLI run.
- Private GHCR images need
read:packageson the PAT.
OCI distribution-spec blobs (image config + each layer) are content-addressed by SHA-256, so caching them on disk is correct by construction: a cache hit on a digest is identical-bytes to a fresh network fetch of that digest. waybill caches every blob it pulls; subsequent scans of the same image complete in seconds rather than tens of seconds.
The image's manifest is intentionally NOT cached — a floating tag like
:latest re-fetches the manifest every time so updates are detected.
Cache location (priority order):
$WAYBILL_OCI_CACHE_DIR(when set non-empty).$XDG_CACHE_HOME/waybill/oci-layers(Linux convention).$HOME/Library/Caches/waybill/oci-layers(macOS).$HOME/.cache/waybill/oci-layers(fallback).
Layout: <cache-dir>/sha256/<64-hex> per blob.
Eviction: default 10 GB cap. Override with --oci-cache-size <bytes>
or WAYBILL_OCI_CACHE_SIZE=<bytes>.
Disabling: pass --no-oci-cache (or set WAYBILL_OCI_CACHE=0) to skip
the cache entirely for one invocation.
Clearing: rm -rf "$XDG_CACHE_HOME/waybill/oci-layers" (or the macOS /
fallback equivalent). waybill doesn't ship a --clear-oci-cache command.
- Quickstart — operator onboarding recipes.
- Configuration — global flags and environment variables.
- SBOM types — the
--sbom-typeflag and CISA SBOM Types vocab. - Identifiers — the four-layer identity model and per-flag identity behavior.
- Cross-tier binding —
--bind-to-source,verify-binding, andtrace-bindingflow. - Generation — per-format builder design.