CDX Command Reference

The cdx command generates one local CycloneDX JSON document without VDB lookup, uploads, memory updates, quality gates, image pulls, or a Docker/Podman daemon.

vulnetix sbom is an alias for vulnetix cdx, not the other way round. CycloneDX is the format this tool produces natively everywhere else — the SBOM in .vulnetix/sbom.cdx.json, VEX attestations, the AIBOM and the CBOM — so “sbom” resolves to CycloneDX by preference. An SPDX generator will land as its own command (vulnetix spdx) rather than changing what sbom means.

It combines:

  • Package manifests and lock files
  • Installed package directories such as node_modules, Python virtualenvs, Go module cache, Composer vendor trees, Ruby gems and NuGet packages
  • Dockerfile/containerfile package installs and container image references
  • CI/CD pipeline install commands (see CI/CD coverage)
  • Shell-script, Makefile and task-recipe package installs
  • Container root filesystem package databases such as dpkg, apk and pacman
  • Packages compiled into binaries: Go build info, Rust cargo auditable data and JVM archive coordinates (see Binary package discovery)
  • AIBOM and CBOM components in the same CycloneDX file
  • Checksums, local signature sidecars and offline transparency-log metadata where present

Usage

vulnetix cdx [path] [flags]

Flags

FlagTypeDefaultDescription
--pathstring.Directory to scan; positional [path] overrides
--depthint25Maximum recursion depth for file discovery
--excludestringArray-Exclude paths matching glob pattern during manifest discovery
--ignorestringArray-Exclude paths matching glob pattern during local inventory discovery
-o, --outputstringprettyTerminal output: pretty, json, cyclonedx-json
--output-filestring<path>/.vulnetix/sbom.cdx.jsonCycloneDX output path
--spec-versionstring1.7CycloneDX spec version: 1.6 or 1.7
--no-manifestsboolfalseSkip package manifest and lockfile parsing
--no-filesystemboolfalseSkip installed-package filesystem discovery
--no-containerfilesboolfalseSkip Dockerfile, compose, Kubernetes and Helm package discovery
--no-ciboolfalseSkip CI/CD pipeline package discovery (including GitHub Actions workflows)
--no-shellboolfalseSkip shell-script, Makefile and task-recipe package discovery
--no-binary-analysisboolfalseSkip binary analysis entirely (file components and embedded package metadata)
--no-binary-packagesboolfalseAnalyse binaries but omit the packages embedded in them
--no-licensesboolfalseSkip license detection for discovered packages
--no-aibomboolfalseOmit AIBOM detection/components
--no-cbomboolfalseOmit CBOM detection/components
--no-signaturesboolfalseSkip signature, attestation and transparency-log sidecar discovery
--include-homeboolfalseInclude user-scoped package caches
--cdx-include-ignoredboolfalseInclude files matched by .gitignore (--sbom-include-ignored is accepted too)
--container-rootfsstringArray-Container rootfs directory to inspect
--container-archivestringArray-Docker/OCI/rootfs tar archive to inspect
--aibom-catalogstring-AIBOM catalog to merge over (or replace) the builtin catalog
--cbom-catalogstring-CBOM catalog to merge over (or replace) the builtin catalog
--no-builtin-aibom-catalogboolfalseUse only --aibom-catalog
--no-builtin-cbom-catalogboolfalseUse only --cbom-catalog

Examples

# Generate .vulnetix/sbom.cdx.json
vulnetix cdx

# Print CycloneDX JSON to stdout and still write the file
vulnetix cdx -o cyclonedx-json

# Inspect an unpacked container filesystem
vulnetix cdx --container-rootfs ./rootfs

# Inspect a saved image tar without using Docker
vulnetix cdx --container-archive ./image.tar --output-file build/image.cdx.json

# Package SBOM only
vulnetix cdx --no-aibom --no-cbom

# Manifests and installed packages only — no inference from pipelines or scripts
vulnetix cdx --no-ci --no-shell --no-containerfiles

Recorded Metadata

Every component carries the same metadata vulnetix sca records, so an offline SBOM and a scanned one describe packages identically:

FieldWhere it lands
Package identityname, version, purl, bom-ref
Ecosystem and scopevulnetix:ecosystem, vulnetix:scope, vulnetix:environment, CycloneDX scope
Direct vs transitivevulnetix:direct, plus urn:project → direct dependency edges
Provenancevulnetix:source-file, vulnetix:source-type, vulnetix:installed-path, vulnetix:version-spec
Registry provenancevulnetix:oci:registryType, vulnetix:oci:private
IntegrityCycloneDX hashes, vulnetix:integrity, vulnetix:gosum-h1
LicensesCycloneDX licenses — an SPDX id when recognised, otherwise a free-text name or expression
Discovery evidencevulnetix:sbom/evidence-count, vulnetix:sbom/evidence (method, confidence, locator)
Signaturesvulnetix:signature/*, vulnetix:tlog/*
Dependency graphCycloneDX dependencies, resolved from lockfile and installed-tree edges
Repository and host contextmetadata.component git properties and metadata.properties host properties

source-type values are manifest (declared in a manifest or lockfile), installed (found in an installed package tree), container-db (an OS package database inside a container root filesystem), command (inferred from a package manager install command in a Dockerfile, CI file or script) and binary (recovered from a compiled artefact).

Evidence confidence reflects how the package was found: high for a declared or installed package and for build-tool-recorded binary metadata, medium for a pinned install command or archive manifest attributes, low for an unpinned install command or a version read off a filename. A command-derived component is an inference — the command may be conditional or never executed — and is graded accordingly.

Existing findings are preserved

The default output path is the same file scan and sca use as scan memory. When that file already carries vulnerabilities (including VEX analysis), cdx carries those entries — and any component they reference but this run did not rediscover, marked vulnetix:sbom/carried-over — into the document it writes. An inventory run never deletes finding history.

CI/CD Coverage

Pipeline-as-code files are parsed for package-manager install commands, and GitHub-Actions-family workflows additionally contribute their uses: pins as github-actions packages.

Detected byFiles
Exact name.gitlab-ci.yml/.yaml, .travis.yml/.yaml, azure-pipelines.yml/.yaml, bitbucket-pipelines.yml/.yaml, buildspec.yml/.yaml (AWS CodeBuild), cloudbuild.yml/.yaml (Google Cloud Build), codefresh.yml/.yaml, appveyor.yml/.yaml, semaphore.yml/.yaml, .semaphore.yml, .drone.yml/.yaml, .woodpecker.yml/.yaml, .cirrus.yml/.yaml, .buddy.yml/.yaml, wercker.yml/.yaml, shippable.yml, bitrise.yml, codemagic.yaml, codeship-steps.yml, screwdriver.yaml/.yml, zuul.yaml/.zuul.yaml, .prow.yaml, harness.yaml, skaffold.yaml/.yml, garden.yml, render.yaml, heroku.yml, vercel.json, netlify.toml, cloud-init.yaml, user-data.yaml, .gitpod.yml/.yaml, devcontainer.json/.devcontainer.json, Taskfile.yml/.yaml, .build.yml/.yaml (SourceHut), .space.kts (JetBrains Space), Jenkinsfile, Jenkinsfile.groovy, Jenkinsfile.declarative, Earthfile
Directory.circleci/, .buildkite/, .semaphore/, .teamcity/, .woodpecker/, .drone/, .azure/, .azure-pipelines/, .pipelines/, .ci/, ci/, .gitlab/ci/, .gitlab-ci/, .tekton/, tekton/, .argo/, .argo-workflows/, .concourse/, concourse/, .harness/, bamboo-specs/, .cirrus/, .codefresh/, .buddy/, .zuul.d/, .prow/, prow/, .builds/, .dagger/, .jenkins/, .spinnaker/, .screwdriver/, .bitbucket/, .codeship/, .devcontainer/, .earthly/, .garden/, .skaffold/, .gocd/, .appveyor/, .bitrise/, .codemagic/, .travis/, .nomad/, .gitpod/ — matching .yml, .yaml, .json, .kts, .groovy or an extensionless config inside them
Name suffix*-pipeline.yml/.yaml, *-pipelines.yml/.yaml, *.gitlab-ci.yml/.yaml, pipeline.yml/.yaml, ci.yml/.yaml, workflow.yml/.yaml, *-workflow.yml/.yaml
Actions family.github/workflows/, .github/actions/, .gitea/workflows/, .forgejo/workflows/, any action.yml/action.yaml

Multi-document YAML streams are parsed in full, so a Tekton Task or an Argo Workflow that is not the first document in the file is still read. Per-vendor command keys are recognised — run, script, commands, cmds, steps, entrypoint, content, args, before_install, build_script, runcmd, installCommand, postCreateCommand and others — because a missed key silently drops every package the pipeline installs.

A line-oriented shell pass also runs over every pipeline file, not only as a fallback. Kotlin (TeamCity, JetBrains Space), Groovy (Jenkins shared libraries) and TOML (netlify.toml) files happen to parse as YAML scalars without error, so a “did it parse as YAML” test would silently skip them; the extra pass also catches commands sitting under a key no vendor list knows about.

A compose file or Kubernetes manifest that happens to live in a CI directory keeps its own, more specific classification.

Shell And Recipe Coverage

Detected byFiles
Extension.sh, .bash, .zsh, .ksh, .ash, .dash, .fish, .bats, .command, .mk, .ps1, .psm1, .cmd, .bat
Exact nameMakefile, makefile, GNUmakefile, justfile/Justfile/.justfile, Vagrantfile
ShebangAny extensionless file starting with a sh, bash or zsh shebang

Line continuations are joined before parsing, so a multi-line apt-get install \ block is read as one command. Linux distro managers (apk, apt, dpkg, yum/dnf/zypper, rpm, pacman), language managers (npm/yarn/pnpm/bun, pip/uv/ poetry/pipenv, gem, composer, cargo, go, nuget/dotnet, mvn/gradle, conda) and the Windows managers used in CI (choco, scoop, winget) are all recognised.

Binary Package Discovery

Compiled artefacts are read for the package metadata their build tool embedded. Nothing is executed, and no bytes beyond those metadata sections are interpreted.

SourceWhat is recovered
Go build info (ELF, PE, Mach-O, XCOFF)Main module, every linked dependency module with its go.sum H1 hash, replacement modules, the toolchain (as stdlib in the Go ecosystem — how Go advisories are indexed), and build settings including vcs.revision
Rust cargo auditable (.dep-v0 ELF section)Every crate with its version and source, build/dev crates scoped as development, plus the crate-level dependency graph
JVM archives (.jar, .war, .ear, .hpi, .jpi)Maven coordinates from META-INF/maven/**/pom.properties, then MANIFEST.MF attributes, then the archive filename; jars nested one level inside a fat/Spring-Boot jar are read too

In a container root filesystem the file lists of the package databases (dpkg, apk, pacman) are read as well, so each binary is attributed to the package that installed it:

  • the owning package component becomes the parent of the file component in the dependency graph
  • the file component records vulnetix:binary/owner-package and vulnetix:binary/owner-ecosystem
  • a binary that no package claims is flagged vulnetix:binary/unpackaged — a file that arrived outside the package manager

RPM databases are Berkeley DB or SQLite files that cannot be read without a native library, so rpm-based images get package components from the package-database pass but no per-file attribution; binaries in those images are not flagged unpackaged.

The same discovery runs during vulnetix containers, which prints what it found and merges the packages into .vulnetix/sbom.cdx.json; disable it there with --no-binary-package-analysis.

Signature And Transparency Log Metadata

cdx looks for local signature and attestation sidecars next to discovered manifest, package, and binary files: .sig, .asc, .minisig, .sigstore, .bundle, .bundle.json, .intoto.jsonl, .att, .attestation and .attestation.json.

When a Sigstore-style bundle carries Rekor transparency-log entries, cdx adds the offline inclusion metadata to the component as CycloneDX properties under vulnetix:tlog/*. It does not perform live Rekor lookups.