Skip to content

Latest commit

 

History

History
2159 lines (1620 loc) · 88.1 KB

File metadata and controls

2159 lines (1620 loc) · 88.1 KB

CLI reference

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 prefer waybill 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 .

Documentation policy

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.


Global flags

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.

--offline

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.json

Accepts 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 a WAYBILL_OFFLINE=1 environment 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.

--exclude-scope <SCOPE[,SCOPE...]>

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.json

When omitted, waybill emits all scopes (Runtime + Development + Build + Test).

--include-declared-deps

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.

--include-legacy-rpmdb

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.json

Modern 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.

--timeout <SECONDS>

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 otherwise

Use 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.

Interaction with other timeouts

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

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: $?" ;;
esac

--exclude-path <PATH_OR_PATTERN>

Skip 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.

When to use it

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.

Classification

Each entry is classified at parse time:

  • Literal path — no glob metacharacters (*, ?, [). Interpreted as a path relative to the scan root. --exclude-path tests/fixtures matches <root>/tests/fixtures and every directory underneath it, but NOT services/a/tests/fixtures.
  • Pattern — contains at least one *, ?, or [. Compiled as a glob; matches directory paths at arbitrary depth. --exclude-path '**/testdata' matches testdata, services/a/testdata, and apps/web/internal/testdata in one entry. Shell-quote the value so your shell doesn't expand * itself.

Examples

Single literal exclusion at repo root:

waybill sbom scan --path /path/to/repo --exclude-path tests/fixtures \
  --format cyclonedx-json --output sbom.cdx.json

Two 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.json

Glob pattern across a monorepo:

waybill sbom scan --path /path/to/monorepo --exclude-path '**/testdata' \
  --format cyclonedx-json --output sbom.cdx.json

Persistent exclusion list via env var (Unix):

export WAYBILL_EXCLUDE_PATH='tests/fixtures:**/testdata'
waybill sbom scan --path . --format cyclonedx-json --output sbom.cdx.json

Transparency annotation

When 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.

Interaction with built-in skips

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.

Troubleshooting

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.

--supplement-cdx <PATH>

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).

When to use it

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 into third_party/ with no Cargo.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.

File format

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.json

The 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.

Honored fields

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.name
  • licenses[], copyright, description
  • externalReferences[] (all types)
  • hashes[], cpe

Per-service:

  • name (required)
  • bom-ref, provider.name, endpoints[], description
  • licenses[], 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.

Hard/soft conflict resolution

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, canonical purl, 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.

Safety property: scanner detection cannot be suppressed

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.

Source-tier marker

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).

Provenance annotation

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.

Fail-closed behavior

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.

Troubleshooting

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.

--no-go-mod-why

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, and waybill: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.json

Classification 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.

--gradle-resolve

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.json

The other four --gradle-* flags (below) are inert without --gradle-resolve.

--gradle-daemon

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.

--gradle-resolve-buildscript

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.

--gradle-timeout-secs <SECONDS>

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.json

--gradle-extra-configurations <NAME>

Extend 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.json

Total 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.


waybill sbom scan

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.

Quick reference

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).

--path <PATH>

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.json

--image <IMAGE>

Container image to scan. Two accepted forms:

  1. 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.
  2. An OCI image reference (e.g., alpine:3.19 or gcr.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.json

For 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-src <SRC[,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 to docker image inspect then docker 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.

--image-platform <linux/ARCH[/VARIANT]>

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.json

Use 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.

--no-oci-cache

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.

--oci-cache-size <BYTES>

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.

--registry-credentials-dir <PATH>

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

  1. 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.ioWAYBILL_REGISTRY_GHCR_IO_USERNAME).
  2. Generic env vars WAYBILL_REGISTRY_USERNAME + WAYBILL_REGISTRY_PASSWORD (applies to every registry).
  3. The --registry-credentials-dir path described above.
  4. $DOCKER_CONFIG/config.json or $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/registry

--output <[FMT=]PATH>

Output 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.json

When omitted, each format writes to its own default filename (waybill.cdx.json, waybill.spdx.json, waybill.spdx3.json).

--split

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 index

Interaction 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.

--output-dir <DIR>

Milestone 215. Directory that receives split-mode sub-SBOMs + split-manifest.json. Required when --split is set; ignored otherwise. Directory is created if missing.

--format <FORMAT>

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 filename waybill.cdx.json.
  • spdx-2.3-json — SPDX 2.3 JSON. Default filename waybill.spdx.json.
  • spdx-3-json — SPDX 3.0.1 JSON-LD. Default filename waybill.spdx3.json.
  • spdx-3-json-experimental (deprecated alias) — byte-identical to spdx-3-json; emits a stderr deprecation notice. Set WAYBILL_NO_DEPRECATION_NOTICE=1 to suppress.

See SBOM types for the per-format SBOM-type field positions and CISA SBOM Types vocab.

--max-file-size <BYTES>

Maximum file size to hash, in bytes. Larger files are skipped. Default 268435456 (256 MB) covers the largest realistic package artifact.

--no-hashes

Omit per-component content hashes from the SBOM. Reduces output size but disables byte-level tamper detection.

--deb-codename <VALUE>

Optional distro codename to stamp on deb PURLs. Overrides the value auto-derived from <root>/etc/os-release (ID + VERSION_IDdistro=<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.json

--no-package-db

Skip 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

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.json

--no-deep-hash

Skip 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).

--json

Print a JSON summary to stdout after writing the SBOM.

waybill sbom scan --path . --output project.cdx.json --json

--no-clearly-defined

Skip 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.

--no-deps-dev

Skip deps.dev license enrichment. Keeps ClearlyDefined and dep-graph enrichment active. The fastest enrichment source and rarely needs skipping.

--no-deps-dev-graph

Skip deps.dev transitive dep-graph enrichment. Keeps deps.dev license enrichment and ClearlyDefined active. Useful when the graph response is large or unneeded.

--enrich-sources <SRC[,SRC...]>

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.json

--bind-to-source <PATH>

Path 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.json

See also: Cross-tier binding for the full algorithm and per-format carrier shapes.

--repo <URL>

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.json

See also: Identifiers for the full identity model.

--git-ref <REVISION>

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.

--image-id <REF>

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.

--attestation <IRI>

Attach an attestation: identifier — in-toto attestation IRI. Manual only; no auto-detection equivalent.

--id <SCHEME=VALUE>

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.json

User-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.

--keep-credentials-in-identifiers

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.

--subject-hash <ALGO:HEX>

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.json

On 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.

--component-id <PURL=SCHEME:VALUE>

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.json

If 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.

--root-name <NAME>

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.json

When 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).

--root-version <VERSION>

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.json

--root-purl-type <TYPE>

Override 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].

--no-root-purl

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.purl field is omitted.
  • SPDX 2.3: the root Package's externalRefs[] contains no entry with referenceType: "purl". The CPE externalRef still emits.
  • SPDX 3: the root software_Package element has no software_packageUrl field AND its externalIdentifier[] contains no entry with externalIdentifierType: "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 ABSENT

The 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 verbatim

--preserve-manifest-main-module

Milestone 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.

--creator <TYPE: NAME>

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 / Person element 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.json

See also: Identifiers for the per-format carrier table.

--annotator <TYPE: NAME> + --annotation-comment <TEXT>

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.json

See also: Identifiers for the annotation landing slots.

--metadata-comment <TEXT>

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.json

--scan-target-name <NAME>

Operator-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.

--metadata-file <PATH>

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.json

--sbom-type <TYPE>

Override 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.json

See also: SBOM types for the four-column equivalence reference and per-format field positions.

--spdx2-relationship-compat <PROFILE>

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 variants DEV_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- direction DEPENDS_ON edge. 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 to DEPENDS_ON makes 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.json

See also: SBOM format mapping rows B2 + C42 for the full cross-format consumer story.


waybill sbom verify

Verify a signed attestation (DSSE envelope) against a key, identity, or in-toto layout.

Quick reference

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.

<ATTESTATION> (positional, required)

Path to a signed .json / .dsse attestation file.

waybill sbom verify build.attestation.json --public-key signer.pub

--public-key <PEM>

PEM-encoded public key expected to have signed the attestation. Mutually exclusive with --identity.

--identity <PATTERN>

Expected signer identity (email, URL, or glob) for keyless-signed attestations.

waybill sbom verify build.attestation.json --identity 'alice@acme.example'

--layout <PATH>

Verify against an in-toto layout. When omitted, only envelope-level checks run (signature + subject).

--expected-subject <PATH>

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-binary

--no-transparency-log

Don't require a Rekor inclusion proof in the envelope. Keyless-mode only.

--fulcio-url <URL> / --rekor-url <URL>

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.

--json

Emit a structured verification report to stdout. Non-zero exit codes: 1 crypto failure, 2 envelope failure, 3 layout failure.


waybill sbom enrich

Add license, VEX, and supplier data to an existing SBOM, applying RFC 6902 JSON Patch ops with per-patch provenance recording.

Quick reference

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.

<SBOM_FILE> (positional, required)

Path to the CycloneDX SBOM to enrich in place.

--patch <PATH>

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"

--author <NAME>

Recorded author of the enrichment. Defaults to unknown with a warning. Lands in the per-patch provenance property.

--output <PATH>

Output path. Defaults to overwriting the input SBOM in place.

--base-attestation <PATH>

Optional path to the attestation the SBOM was derived from. Its SHA-256 gets embedded so verifiers can walk back to the attested source.

--vex-overrides <PATH>

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 <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 with waybill: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-licenses / --skip-supplier / --skip-vex

Skip the corresponding enrichment phase. Useful for narrow re-runs (e.g., only re-enrich VEX after a triage pass).

--deps-dev-timeout <MS>

Timeout per deps.dev API call, in milliseconds. Default 5000.

--json

Print a JSON summary to stdout.


waybill sbom verify-binding

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.

Quick reference

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.

--image-sbom <PATH>

Path to the image-tier SBOM (JSON). Required.

--source-sbom <PATH>

Path to the source-tier SBOM (JSON). Required.

--format <FORMAT>

Output format. Possible values:

  • table (default) — plain-text per-row table.
  • jsonVerifyReport JSON for CI pipelines / machine consumption.
waybill sbom verify-binding \
    --image-sbom image.cdx.json \
    --source-sbom source.cdx.json \
    --format json

See also: Cross-tier binding for the verification algorithm.


waybill sbom trace-binding

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).

Quick reference

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.

--component-purl <PURL>

PURL of the component to trace. Required.

--image-sbom <PATH>

Image-tier SBOM (CDX/SPDX 2.3/SPDX 3 JSON). Required.

--source-sbom <PATH>

Single candidate source-tier SBOM. Mutually exclusive with --candidate-sources-dir.

--candidate-sources-dir <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-sboms

--format <FORMAT>

Output format. Possible values:

  • table (default) — plain-text per-row table.
  • jsonTraceReport JSON for CI pipelines / machine consumption.

See also: Cross-tier binding for the binding-state vocabulary.


waybill trace run

Status: experimental. Linux-only. Adds ~2-3× wall-clock overhead on syscall-heavy builds; requires CAP_BPF + CAP_PERFMON; coverage gaps on openat2 and io_uring. Prefer waybill sbom scan unless you need a trace-bound attestation.

Capture a build trace and produce both an SBOM and an in-toto attestation in one step.

Quick reference

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.

<COMMAND>... (positional, required)

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 ripgrep

--sbom-output <PATH>

SBOM output path. Default waybill.cdx.json.

--attestation-output <PATH>

Attestation output path. Default waybill.attestation.json.

--format <FORMAT>

SBOM output format. Default cyclonedx-json. See waybill sbom scan --format for the registered format set.

--no-enrich

Skip the enrichment step entirely (no deps.dev / ClearlyDefined calls).

--include-source-files

Also include observed source files (not just packages). Switches SBOM scope from packages to source.

--trace-children

Follow forked children of the traced command. Useful when the build command spawns subprocesses (cargo → rustc, npm → node, etc.).

--libssl-path <PATH>

Override libssl.so path for uprobe attachment. Default: auto-detect from the build's process environment.

--ring-buffer-size <BYTES>

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.

--timeout <SECONDS>

Trace timeout in seconds. 0 means no timeout. Useful for unattended CI runs that should not hang indefinitely.

--skip-purl-validation

Skip the online PURL existence validation step.

--lockfile <PATH>

Path to a lockfile for dependency-relationship enrichment. Auto-detects format (Cargo.lock, package-lock.json, go.sum). Unrecognised formats are logged and skipped.

--artifact-dir <DIR> / --auto-dirs

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:

  • --repo and --git-ref auto-detect from git remote get-url when the invocation cwd is a git checkout; the flag overrides the auto-detected value.
  • --subject-hash augments the auto-detected subject set from the in-toto attestation envelope (deduplicated by exact match).

See also: Identifiers for build-tier auto-detection semantics.

--signing-key <PATH>

Path to a PEM-encoded private key for local-key DSSE signing. Mutually exclusive with --keyless.

--signing-key-passphrase-env <NAME>

Env var name holding the passphrase for an encrypted --signing-key. No effect on unencrypted keys. No interactive prompt — CI-friendly by design.

--keyless

Keyless signing via OIDC → Fulcio → Rekor. Mutually exclusive with --signing-key. Auto-detects GitHub Actions OIDC tokens.

--fulcio-url <URL> / --rekor-url <URL>

Override the Fulcio / Rekor URLs. Defaults https://fulcio.sigstore.dev and https://rekor.sigstore.dev.

--no-transparency-log

Skip Rekor upload + inclusion-proof embedding. Keyless-mode only.

--require-signing

Fail if no signing identity was configured. Flips the default "emit unsigned + warn" behavior to a hard error.

--subject <PATH>

Explicit subject artifact path. Repeatable. When set, auto-detection is suppressed — waybill signs exactly what you told it to.

--attestation-format <FORMAT>

Attestation output format. Possible values:

  • witness-v0.1 (default) — in-toto Statement v0.1 wrapped around a witness attestation-collection (material + command-run + product
    • network-trace inner attestors). Directly consumable by sbomit generate and any go-witness-aware verifier.
  • waybill-v1 — waybill's native BuildTracePredicate Statement v1. Richer network-trace semantics but only waybill understands it.

waybill trace capture

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).

Quick reference

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.

--target-pid <PID>

Existing process ID to attach the trace to. Mutually exclusive with the -- <COMMAND> form.

--output <PATH>

Attestation output path. Default waybill.attestation.json.

--artifact-dir <DIR>

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-dirs

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.

--go-binary <PATH>

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).

Other flags

--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.


waybill policy init

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.

Quick reference

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.

--functionary-key <PATH>

PEM-encoded public key of the expected signer. Required.

waybill policy init --functionary-key signer.pub --output layout.json

--output <PATH>

Where to write the layout. Default layout.json.

--step-name <NAME>

Name of the single step the layout expects. Default build-trace-capture.

--expires <DURATION>

How long the layout is valid. Default 1y. Accepted suffixes: y (years), m/mo (months), d (days), w (weeks).

--readme <TEXT>

Optional human-readable description embedded in the layout.


Output formats

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.


Authenticating to private registries

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:

  1. credHelpers.<registry> — per-registry helper override (AWS ECR via docker-credential-ecr-login, Google Artifact Registry via docker-credential-gcloud).
  2. credsStore — registry-wide helper (osxkeychain, wincred, secretservice, pass, desktop).
  3. auths.<registry>.auth — direct credentials, base64-encoded as user:password.
  4. 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:packages on the PAT.

OCI layer caching

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

  1. $WAYBILL_OCI_CACHE_DIR (when set non-empty).
  2. $XDG_CACHE_HOME/waybill/oci-layers (Linux convention).
  3. $HOME/Library/Caches/waybill/oci-layers (macOS).
  4. $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.


See also

  • Quickstart — operator onboarding recipes.
  • Configuration — global flags and environment variables.
  • SBOM types — the --sbom-type flag and CISA SBOM Types vocab.
  • Identifiers — the four-layer identity model and per-flag identity behavior.
  • Cross-tier binding--bind-to-source, verify-binding, and trace-binding flow.
  • Generation — per-format builder design.