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

# Keep an existing SBOM current

> Refresh the vulnerability and licence data of a released inventory or SBOM every week with scanoss-cli enrich, without the source code and without re-scanning, and fail when new advisories appear.

## Goal

A release does not change after you ship it, but the list of known vulnerabilities in its
components does. New advisories come out every day. This recipe takes the inventory you stored at release
time, refreshes its vulnerability and licence data every week, and reports the advisories that are
new since the last run.

## When to use it

* You ship releases that customers run for months or years, and you must know when a new
  vulnerability affects one of them.
* You keep the inventory or SBOM from each release (for example, from the
  [release SBOM recipe](/en/latest/developer-tools/recipes/release-sbom)).
* The job that runs the refresh may not have the source code. `enrich` does not need it.

## How enrich works

`scanoss-cli enrich` reads an existing inventory or SBOM, looks up each component by its PURL
through the SCANOSS API, and writes the file again with fresh data. It does not fingerprint or
scan anything, so it is quick, and you can run it as often as you like on the same file.

| Input (detected from the content) | Output by default |
| - | - |
| scanoss-cli raw inventory | raw |
| CycloneDX JSON | CycloneDX |
| SPDX JSON | SPDX |

Choose the layers with `--include`: `vulns`, `licenses`, `crypto`, and `geo`. Use `-f, --format`
to write a different format in the same pass.

## Prerequisites

* scanoss-cli, `jq`, and `SCANOSS_API_KEY` (see [Recipes](/en/latest/developer-tools/recipes#before-you-use-a-recipe))
* A stored inventory, for example `inventory-2.4.0.json` from a release
* A scheduler: your CI system's scheduled pipelines, or `cron`

## The script

Save this as `ci/scanoss-refresh.sh`.

```bash theme={null}
#!/usr/bin/env bash
# Refresh vulnerabilities and licences on a stored inventory, and fail when
# advisories at or above a severity appear that were not there last time.
#
# Usage: ci/scanoss-refresh.sh <inventory.json> [<previous-refresh.json>]
#   FAIL_ON_SEVERITY  lowest severity of a NEW advisory that fails the job:
#                     critical, high, medium, low (default: high)
set -euo pipefail

INVENTORY="${1:?usage: $0 <inventory.json> [previous-refresh.json]}"
PREVIOUS="${2:-$INVENTORY}"
FAIL_ON_SEVERITY="${FAIL_ON_SEVERITY:-high}"
refreshed="${INVENTORY%.json}.refreshed-$(date +%F).json"
log="$(mktemp)"

# 1. Refresh. enrich exits 0 even when a lookup service fails, so check its log.
scanoss-cli enrich "$INVENTORY" --include vulns,licenses --output "$refreshed" 2> "$log" \
  || { cat "$log" >&2; exit 1; }
cat "$log" >&2
if grep -qE "Enrichment incomplete|layer returned no data for some components" "$log"; then
  echo "Refresh incomplete; keeping the previous data. See the log above." >&2
  rm -f "$refreshed"
  exit 1
fi

# 2. List advisories that were not in the previous file.
new_advisories="$(jq -r --slurpfile prev "$PREVIOUS" '
  ($prev[0].vulnerabilities // [] | map(.id)) as $known
  | .vulnerabilities[]?
  | select(.id | IN($known[]) | not)
  | "\(.id)\t\(.severity // "unknown")\t\(.purls // [] | join(", "))"
' "$refreshed")"

echo "Refreshed inventory: $refreshed"
if [ -z "$new_advisories" ]; then
  echo "No new advisories since $(basename "$PREVIOUS")."
  exit 0
fi
echo "New advisories since $(basename "$PREVIOUS"):"
echo "$new_advisories"

# 3. Fail when a new advisory is at or above the threshold.
blocking="$(jq -r --slurpfile prev "$PREVIOUS" --arg min "$FAIL_ON_SEVERITY" '
  {"critical": 4, "high": 3, "medium": 2, "low": 1} as $rank
  | ($prev[0].vulnerabilities // [] | map(.id)) as $known
  | .vulnerabilities[]?
  | select(.id | IN($known[]) | not)
  | select(($rank[(.severity // "") | ascii_downcase] // 0) >= $rank[$min])
  | .id
' "$refreshed")"

if [ -n "$blocking" ]; then
  echo "New advisories at or above '$FAIL_ON_SEVERITY':" $blocking
  exit 2
fi
```

Run it every week against the latest refresh, so each run reports only what is new since the
week before:

```bash theme={null}
# First run: compare against the release inventory itself
ci/scanoss-refresh.sh sbom/inventory-2.4.0.json

# Later runs: compare against last week's refresh
ci/scanoss-refresh.sh sbom/inventory-2.4.0.json sbom/inventory-2.4.0.refreshed-2026-09-29.json
```

Store each refreshed file where the next run can find it, for example in the same artefact store
as the release.

## What each step does

1. **Refresh.** `enrich` replaces the vulnerability and licence data of every component with the
   current data. It does not change the components or their evidence. If a lookup fails, `enrich` still
   writes a file and exits `0`, but prints a warning on stderr: `Enrichment incomplete` when a
   whole layer failed, or `layer returned no data for some components` when part of it failed.
   The script treats both as a failure, so you never compare against partial data.
2. **Compare.** `jq` reads the previous file with `--slurpfile` and lists every advisory ID in the
   new file that the previous file did not have.
3. **Gate.** Only new advisories at or above `FAIL_ON_SEVERITY` fail the job. Known advisories,
   which you have already seen, do not fail it again.

## What the output means

```
Refreshed inventory: sbom/inventory-2.4.0.refreshed-2026-10-06.json
New advisories since inventory-2.4.0.refreshed-2026-09-29.json:
CVE-2026-12345	high	pkg:github/example/libfoo@1.4.2
New advisories at or above 'high': CVE-2026-12345
```

Each line lists the advisory ID, its severity, and the affected components of that release. Use
it to decide whether to ship a patch release. To see every advisory that affects the release,
not only the new ones, run the
[licence and vulnerability gate](/en/latest/developer-tools/recipes/licence-and-vulnerability-gate)
on the refreshed file.

## How it fails the pipeline

| Exit code | Meaning |
| - | - |
| `0` | Refresh succeeded, and no new advisory reached the threshold |
| `1` | The refresh failed or was incomplete (no key, API unreachable, a lookup failed) |
| `2` | At least one new advisory at or above `FAIL_ON_SEVERITY` |

## Schedule it

Any scheduler works. With `cron`, on a machine that has the files and the key:

```bash theme={null}
# Every Monday at 06:00
0 6 * * 1  cd /srv/releases && SCANOSS_API_KEY="$(cat /etc/scanoss/key)" ci/scanoss-refresh.sh sbom/inventory-2.4.0.json
```

In a CI system, use its scheduled pipelines and loop over the releases you still support.

## Refresh an SBOM instead of the raw inventory

`enrich` also reads CycloneDX and SPDX files, and the comparison above works on raw files. For a
CycloneDX SBOM, refresh it in place:

```bash theme={null}
scanoss-cli enrich sbom-2.4.0.cdx.json --include vulns,licenses --output sbom-2.4.0.refreshed.cdx.json
```

SPDX 2.3 cannot hold vulnerabilities, so `enrich` skips the `vulns` layer for SPDX output and prints a
notice. To get vulnerability data for a release you only have as SPDX, write CycloneDX in the same
pass:

```bash theme={null}
scanoss-cli enrich sbom-2.4.0.spdx.json --include vulns,licenses --format cyclonedx \
  --output sbom-2.4.0.refreshed.cdx.json
```

<Note>
  `enrich` cannot add the `deps` layer, because declared dependencies come from manifest files in
  the source tree. Include `deps` when you first scan the release, as the
  [release SBOM recipe](/en/latest/developer-tools/recipes/release-sbom) does.
</Note>


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