> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scanoss.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate an SBOM for each release

> Scan a release once with scanoss-cli, then produce SPDX 2.3 and CycloneDX 1.7 SBOMs from the same result, offline, ready to publish with the release.

## Goal

For every release, publish a Software Bill of Materials (SBOM) in both common formats:

* CycloneDX 1.7, with components, licences, evidence, and known vulnerabilities
* SPDX 2.3, with components and licences

Keep the scanoss-cli raw inventory as well, so you can
[refresh the SBOM later](/en/latest/developer-tools/recipes/refresh-sbom-with-enrich) without
scanning again.

## When to use it

* A customer, a regulation, or your own policy asks for an SBOM with each release.
* You want a single record of what a release contained, stored next to the release.

## Prerequisites

* scanoss-cli, `jq`, and `SCANOSS_API_KEY` (see [Recipes](/en/latest/developer-tools/recipes#before-you-use-a-recipe))
* The source tree checked out at the release tag
* Optional: a `scanoss.json` with your approvals, so the SBOM marks approved components

## The script

Save this as `ci/scanoss-release-sbom.sh` and run it from the repository root in your release
job.

```bash theme={null}
#!/usr/bin/env bash
# Produce the SBOMs for one release.
# Usage: ci/scanoss-release-sbom.sh <version>   e.g. ci/scanoss-release-sbom.sh 2.4.0
set -euo pipefail

VERSION="${1:?usage: $0 <version>}"
OUT="sbom"
mkdir -p "$OUT"

inventory="${OUT}/inventory-${VERSION}.json"
cyclonedx="${OUT}/sbom-${VERSION}.cdx.json"
spdx="${OUT}/sbom-${VERSION}.spdx.json"

# 1. One scan, with the declared dependencies, licences, and vulnerabilities.
scanoss-cli scan . \
  --include deps,licenses,vulns \
  --output "$inventory"

# 2. Convert offline: no second scan, no API calls.
scanoss-cli sbom "$inventory" --format cyclonedx --output "$cyclonedx"
scanoss-cli sbom "$inventory" --format spdx      --output "$spdx"

# 3. Sanity checks before publishing.
jq -e '.bomFormat == "CycloneDX"' "$cyclonedx" > /dev/null
jq -e '.spdxVersion | startswith("SPDX-2.")' "$spdx" > /dev/null

# 4. Checksums, so consumers can verify the files.
( cd "$OUT" && sha256sum "$(basename "$inventory")" "$(basename "$cyclonedx")" "$(basename "$spdx")" > "SHA256SUMS-${VERSION}" )

jq -r '"Components: \(.components | length)   Vulnerabilities: \(.vulnerabilities // [] | length)"' "$inventory"
```

## What each step does

1. **Scan once.** `scan .` fingerprints the release, matches it against the SCANOSS Knowledge
   Base, and writes the raw inventory. `--include` adds three layers:
   * `deps`: dependencies declared in manifest files such as `package.json` or `go.mod`,
     resolved through the API. They appear with `"scope": "declared"`.
   * `licenses`: the licences of every component, detected or declared.
   * `vulns`: known vulnerabilities for every component.
2. **Convert.** `scanoss-cli sbom` reads the inventory and writes the requested format. It does
   not contact the API, so it is fast and gives the same result each time. `--format` is
   required.
3. **Check.** `jq -e` exits non-zero if a file is not the expected format, so the job stops
   before it publishes a broken SBOM.
4. **Checksums.** A `SHA256SUMS` file lets consumers confirm they have the files you published.

Attach the files in `sbom/` to the release, or upload them to wherever you store release
artefacts.

## What the output means

| File | Contains | Use it for |
| - | - | - |
| `inventory-<version>.json` | The scanoss-cli raw inventory: every component with its evidence, licences, and a flat vulnerability list. Includes all layers. | Your own record, and the input for a later [refresh](/en/latest/developer-tools/recipes/refresh-sbom-with-enrich) |
| `sbom-<version>.cdx.json` | CycloneDX 1.7: components, licences, evidence, vulnerabilities | Security tooling and customers that ask for CycloneDX |
| `sbom-<version>.spdx.json` | SPDX 2.3: components and licences. The CLI combines several licences on one component with `AND`. | Licence compliance and customers that ask for SPDX |

What each format can hold differs:

| Layer | raw | CycloneDX | SPDX |
| - | - | - | - |
| Components and evidence | Yes | Yes | Yes |
| Licences | Yes | Yes | Yes |
| Vulnerabilities | Yes | Yes | No. The CLI leaves them out and prints a warning. |
| Cryptography, geoprovenance | Yes | No | No |

A component that is both detected in your code and declared in a manifest appears once. Components
approved in `scanoss.json` carry the `scanoss:identified` property in CycloneDX.

## How it fails the pipeline

The script uses `set -euo pipefail`, so it stops at the first failing command:

| Failure | Exit code |
| - | - |
| The scan fails (no key, API unreachable) | `1`, from scanoss-cli |
| A conversion fails | `1`, from scanoss-cli |
| A file is not the expected format | `1`, from `jq -e` |

An SBOM documents what a release contains. It does not decide whether the release is allowed. To
block a release on licences or vulnerabilities, run the
[licence and vulnerability gate](/en/latest/developer-tools/recipes/licence-and-vulnerability-gate)
on `inventory-<version>.json` before you publish:

```bash theme={null}
ci/scanoss-policy.sh "sbom/inventory-${VERSION}.json"
```

## Variations

* **One format only.** Skip the raw inventory and scan straight into a format:
  `scanoss-cli scan . --include deps,licenses,vulns --format cyclonedx --output sbom.cdx.json`.
  You lose the raw inventory, but you can still [refresh](/en/latest/developer-tools/recipes/refresh-sbom-with-enrich)
  the CycloneDX file later.
* **Convert an SBOM you already have.** `scanoss-cli sbom` also converts between CycloneDX and
  SPDX: `scanoss-cli sbom bom.cdx.json --format spdx --output bom.spdx.json`.
* **Run in a container.** See [Using Docker](/en/latest/cli/scanoss-go/using-docker).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.