Augmentation: Adding Your Business Metadata to an SBOM

How to add supplier, author, license, lifecycle and security contact information to your SBOM with sbomify.json, plus automatic VCS detection from your CI environment.

Augmentation vs. enrichment

Two different problems, two different mechanisms:

AugmentationEnrichment
AddsMetadata about your softwareMetadata about your dependencies
SourceYou, via sbomify.json or the sbomify APIPublic package registries
ScopeThe document and its root componentEvery component
Account requiredNoNo
Enable withAUGMENT: trueENRICH: true

Nobody but you knows who supplies your software, when its support window ends, or where to report a vulnerability in it. That is augmentation. What license requests uses is public knowledge - that is enrichment.

Most projects want both.

The config file

Create sbomify.json in your project root and set AUGMENT: true. No account needed.

{
  "lifecycle_phase": "build",
  "supplier": {
    "name": "My Company",
    "url": ["https://example.com"],
    "contacts": [{"name": "Support", "email": "support@example.com"}]
  },
  "authors": [
    {"name": "Jane Doe", "email": "jane@example.com"}
  ],
  "licenses": ["Apache-2.0"],
  "security_contact": "https://example.com/.well-known/security.txt",
  "release_date": "2026-06-15",
  "support_period_end": "2028-12-31",
  "end_of_life": "2030-12-31"
}

Field reference

FieldDescriptionWhere it lands
lifecycle_phaseThe context this SBOM was generated inCycloneDX metadata.lifecycles[].phase; SPDX creationInfo.creatorComment
supplierThe organisation supplying the componentCycloneDX metadata.supplier; SPDX packages[].supplier
authorsWho authored the componentCycloneDX metadata.authors[]; SPDX creationInfo.creators[]
licensesSPDX license identifiers for your own softwareCycloneDX metadata.licenses[]; SPDX document licenses
security_contactWhere to report vulnerabilitiesCycloneDX externalReferences[type=security-contact]; SPDX externalRefs[category=SECURITY]
release_dateWhen this version was releasedCycloneDX lifecycle property; SPDX external ref
support_period_endWhen security-only support endsCycloneDX lifecycle property; SPDX validUntilDate
end_of_lifeWhen all support endsCycloneDX lifecycle property; SPDX external ref
vcs_urlRepository URL, overriding CI detectionCycloneDX externalReferences[type=vcs]; SPDX downloadLocation
vcs_commit_shaFull commit SHAAppended to the VCS URL
vcs_refBranch or tag nameAdded as build context

Valid values

lifecycle_phase is one of design, pre-build, build, post-build, operations, discovery, decommission. Most CI pipelines want build. When the input is a container image, post-build is set automatically.

security_contact accepts a security.txt URL (recommended), a mailto: address, or a disclosure procedure URL.

Dates are ISO-8601, for example 2028-12-31. The three lifecycle dates mean different things:

  • release_date - when this version became publicly available
  • support_period_end - bugfixes stop, security patches continue
  • end_of_life - no further updates of any kind

These map onto Common Lifecycle Enumeration, and let downstream consumers automatically flag software that is approaching or past end of support.

Why this matters for compliance

The NTIA Minimum Elements require a supplier name and an author of the SBOM data. The EU Cyber Resilience Act expects a documented vulnerability reporting channel and a defined support period. None of that can be inferred from a lockfile - a scanner has no way to know it.

Augmentation is how those fields get populated, and it is the difference between an SBOM that passes a procurement review and one that gets sent back.

Where values come from

Sources are consulted in priority order, and local values always win:

  1. sbomify.json in your project root. Highest priority. No account required.
  2. The sbomify API, using metadata configured on your component. Requires COMPONENT_ID and credentials.
  3. CI environment detection, which fills in VCS information automatically.

Keeping metadata in sbomify.json means it is version-controlled and reviewable alongside your code. Keeping it in sbomify means you can change it without a commit. Local wins, so you can override centrally-managed values per repository.

By default augmentation only fills fields that are empty. Set OVERRIDE_SBOM_METADATA: true to make it overwrite values the generator already produced - useful when a generator guesses a name or version you would rather correct.

Automatic VCS detection

Repository URL, commit SHA and branch or tag are detected and added automatically on every CI runtime. No configuration needed.

Where a vendor publishes those details as environment variables, they are read from there. Everywhere else the action reads them from the git checkout it is running in, which is why Azure Pipelines and any other container runner get provenance without you writing it out by hand.

RuntimeSourceNotes
GitHub ActionsRunner environmentWorks with GitHub Enterprise Server
GitLab CIJob environmentWorks with self-managed instances
Bitbucket PipelinesStep environmentBitbucket Cloud; Data Center needs vcs_url in sbomify.json
TeamCityBuild properties fileGit roots only - see below
JenkinsJob environment, then the checkoutGit plugin variables; the checkout on a Subversion, Perforce or SCM-less job
CircleCIJob environment, then the checkoutThe checkout when the job never ran checkout
Travis CIJob environment for the ref, checkout for the URLTravis publishes no repository URL - see below
Azure Pipelines, Buildkite, Drone, AppVeyor, AWS CodeBuild, any other CIThe git checkout (git remote, git rev-parse)Needs the .git directory in the container and a configured remote
Your own machineThe git checkout, opt-inSet SBOMIFY_LOCAL_VCS=true - see below

Jenkins, CircleCI and Travis CI got platforms of their own after v26.8.0; on older tags all three read the checkout, which is still what each falls back to. The reason is the ref: all three check out a detached HEAD, so the checkout can name a tag that happens to point at the commit and nothing else, while the vendor knows which branch the build was for - and on a pull request, which branch the request came from.

Each records repository URL, commit SHA and branch or tag. A browsable commit URL is added as well for github.com, gitlab.com and bitbucket.org, plus self-hosted GitHub and GitLab, whose commit paths match their cloud products. Everything else - Bitbucket Data Center included, since it lays commit URLs out differently from Bitbucket Cloud - gets the repository URL and the SHA without a link, rather than a guessed one that 404s.

What gets written:

  • CycloneDX - a VCS external reference on the root component, in git+https://...@sha form
  • SPDX - downloadLocation pinned to the commit, plus sourceInfo with build context and a VCS external reference

This is what makes an SBOM traceable back to a specific commit, which in turn is what makes signing meaningful: the document says exactly which source produced it.

TeamCity is different

TeamCity is VCS-agnostic, and a root can just as easily be Subversion, Perforce or TFVC as Git. Under those, its revision parameter holds a changelist or revision number rather than a commit hash, and the SBOM’s VCS fields are Git-shaped - so writing one into the other would put a false claim into a document you may go on to sign.

TeamCity exposes no parameter saying which VCS a root uses. Detection therefore runs only when the repository URL positively identifies Git, and emits nothing otherwise. A self-hosted Git server whose URL has neither a .git suffix nor a recognised host cannot be detected; set SBOMIFY_VCS_URL (and SBOMIFY_VCS_REF) and it is trusted as given. See the TeamCity runtime guide.

Travis CI is half and half

Travis publishes the commit and the branch, but no repository URL - only TRAVIS_REPO_SLUG, an owner/repo pair with no host attached. Travis serves GitHub, Bitbucket, GitLab and Assembla projects, so turning that pair into a URL means guessing the forge, and a link that lands on the wrong host is worse than no link. The URL therefore comes from the checkout, and the commit and ref from the job. Nothing is constructed from the slug, and CircleCI’s CIRCLE_PROJECT_USERNAME/CIRCLE_PROJECT_REPONAME pair is left alone for the same reason.

Travis jobs run on a VM rather than in a container of your choosing, so a docker run has to forward the variables explicitly. See Travis CI.

Runtimes read from the checkout

Azure Pipelines and any other container runner have no vendor integration, and none is needed: they check out a git repository and run a command in it, so the action asks git directly. The same path is what Jenkins, CircleCI and Travis CI fall back to. Two conditions have to hold, and both are the default:

  • The .git directory has to be there. A shallow clone is fine; an exported tarball or a mount of only your lockfile is not.
  • The repository needs a remote. origin is used if present, otherwise the first remote. A checkout with no remote has no URL worth recording, so nothing is emitted.

A runner is recognised as CI when it sets CI=true or a vendor variable of its own (CIRCLECI, TRAVIS, TF_BUILD, BUILDKITE, DRONE, APPVEYOR, CODEBUILD_BUILD_ID, or any of the five Jenkins markers below). Almost every runner sets one; a bare docker run from a shell script sets neither and is treated as a local run.

Jenkins is detected on any of JENKINS_URL, JENKINS_HOME, HUDSON_HOME, JENKINS_NODE_COOKIE or JENKINS_SERVER_COOKIE, rather than JENKINS_URL alone. A Docker Pipeline agent - the documented way to run the action on Jenkins - forwards the build environment into the container, which carries HUDSON_HOME and both cookies but no JENKINS_URL, since Jenkins only exports that once the instance URL has been filled in under system settings. A freestyle build is the other way round.

If any of that does not hold, or the remote URL is not the one you want in the document, set the fields yourself:

{
  "vcs_url": "https://github.com/my-org/my-repo",
  "vcs_commit_sha": "abc123def456",
  "vcs_ref": "main"
}

sbomify.json takes priority over detection, so this is also how you replace an internal remote URL with the public one.

Local runs are opt-in

On your own machine nothing is read from the checkout unless you ask for it. The same lock file would otherwise produce a different SBOM depending on whether a remote happened to be configured, and an internal remote would be written into a document that often leaves the company.

Set SBOMIFY_LOCAL_VCS=true to opt in, or state the fields in sbomify.json as above. On CI it stays automatic - that is the point of it.

Overriding or disabling

The same three fields override auto-detected values, which is useful for self-hosted forges whose URLs are not derivable from the environment.

To turn detection off entirely:

env:
  DISABLE_VCS_AUGMENTATION: "true"

Component identity overrides

Independently of sbomify.json, three variables override the root component’s identity:

env:
  COMPONENT_NAME: my-app
  COMPONENT_VERSION: ${{ github.ref_name }}
  COMPONENT_PURL: pkg:golang/github.com/my-org/my-app@1.2.3

COMPONENT_VERSION is the one most projects need, because generators often infer a version from a manifest rather than from the release you are actually shipping. See how to version SBOMs for what to put there.

Every override is recorded in the audit trail, so the fact that a value was changed - and what it was before - is preserved.