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

# Fail the build on a disallowed licence or a known vulnerability

> A small policy script over the scanoss-cli scan result: fail when a component carries a licence on your deny-list, or a known vulnerability at or above a severity you choose.

## Goal

Turn a scan result into a pass or fail decision based on two rules:

1. No component may carry a licence that your organisation does not allow.
2. No component may have a known vulnerability at or above a chosen severity.

## When to use it

* In a pull request job, after the [pull request scan](/en/latest/developer-tools/recipes/scan-pull-requests).
* In a release job, before you publish an SBOM.
* In a scheduled job over a [refreshed inventory](/en/latest/developer-tools/recipes/refresh-sbom-with-enrich),
  to catch vulnerabilities disclosed after release.

scanoss-cli reports what it finds and exits `0` whatever it finds. It does not decide what is
allowed. This recipe writes that decision down as a script.

## Prerequisites

* scanoss-cli, `jq`, and `SCANOSS_API_KEY` (see [Recipes](/en/latest/developer-tools/recipes#before-you-use-a-recipe))
* A scan result in the raw format (the default) that includes the `licenses` and `vulns`
  layers:

  ```bash theme={null}
  scanoss-cli scan . --include licenses,vulns --output scanoss-results.json
  ```

  Add `deps` (`--include deps,licenses,vulns`) to also check the dependencies declared in your
  manifest files, not only the components detected in your code.
* `scanoss/unapproved.jq` committed to the repository (see [Approve components](/en/latest/developer-tools/recipes/approve-components-bom-rules#how-it-fails-the-pipeline))

<Warning>
  Without `--include licenses,vulns`, the result has no licence or vulnerability data and this
  check always passes. Always produce the input with both layers.
</Warning>

## The script

Save this as `ci/scanoss-policy.sh` and make it executable.

```bash theme={null}
#!/usr/bin/env bash
# Fail when a scanoss-cli raw result contains a disallowed licence, or a
# known vulnerability at or above a severity threshold.
#
# Usage: ci/scanoss-policy.sh scanoss-results.json
#
# Environment:
#   DENY_LICENSES      regular expression matched against SPDX licence IDs
#                      (default: GPL, AGPL and SSPL families, not LGPL)
#   FAIL_ON_SEVERITY   lowest severity that fails: critical, high, medium, low
#                      (default: high)
#   SKIP_IDENTIFIED    "true" exempts components approved in scanoss.json
#                      from the licence rule (default: false). A rule that
#                      pins a version exempts only that version.
set -euo pipefail

RESULTS="${1:?usage: $0 <scanoss-results.json>}"
DENY_LICENSES="${DENY_LICENSES:-^(AGPL|GPL|SSPL)-}"
FAIL_ON_SEVERITY="${FAIL_ON_SEVERITY:-high}"
SKIP_IDENTIFIED="${SKIP_IDENTIFIED:-false}"

# Components scanoss.json doesn't approve (filter from the "Approve components" recipe).
rules=scanoss.json; [ -f "$rules" ] || rules=/dev/null
unapproved="$(jq -c --slurpfile rules "$rules" -f scanoss/unapproved.jq "$RESULTS" \
  | jq -cs 'map("\(.purl)@\(.version)")')"

case "$FAIL_ON_SEVERITY" in
  critical|high|medium|low) ;;
  *) echo "FAIL_ON_SEVERITY must be critical, high, medium, or low" >&2; exit 1 ;;
esac

# Rule 1: licences on the deny-list.
licence_hits="$(jq -r --arg deny "$DENY_LICENSES" --arg skip "$SKIP_IDENTIFIED" \
    --argjson unapproved "$unapproved" '
  .components[]
  | select($skip != "true" or (.identified | not)
           or ("\(.purl)@\(.version)" as $key | $unapproved | index($key)))
  | [.licenses[]?.id | select(test($deny))] as $bad
  | select($bad | length > 0)
  | "\(.purl)@\(.version // "unknown")  \($bad | unique | join(", "))"
' "$RESULTS")"

# Rule 2: vulnerabilities at or above the threshold.
vuln_hits="$(jq -r --arg min "$FAIL_ON_SEVERITY" '
  {"critical": 4, "high": 3, "medium": 2, "low": 1} as $rank
  | .vulnerabilities[]?
  | select(($rank[(.severity // "") | ascii_downcase] // 0) >= $rank[$min])
  | "\(.id)  \(.severity)  \(.purls // [] | join(", "))"
' "$RESULTS")"

status=0
if [ -n "$licence_hits" ]; then
  echo "Disallowed licences (pattern: $DENY_LICENSES):"
  echo "$licence_hits"
  status=2
fi
if [ -n "$vuln_hits" ]; then
  echo "Vulnerabilities at or above '$FAIL_ON_SEVERITY':"
  echo "$vuln_hits"
  status=2
fi
[ "$status" -eq 0 ] && echo "Licence and vulnerability policy passed."
exit "$status"
```

Run it after the scan:

```bash theme={null}
scanoss-cli scan . --include licenses,vulns --output scanoss-results.json
ci/scanoss-policy.sh scanoss-results.json
```

## How the rules read the result

Both rules read fields of the scanoss-cli raw inventory:

| Rule | Field | Notes |
| - | - | - |
| Licences | `components[].licenses[].id` | SPDX licence IDs, such as `GPL-2.0-only` or `MIT`. Unknown licences can appear as IDs that start with `LicenseRef-`. |
| Vulnerabilities | `vulnerabilities[].severity` | `critical`, `high`, `medium`, `low`, or `none`, in any letter case. Entries without a severity never fail the rule. |
| Vulnerabilities | `vulnerabilities[].purls` | The affected components, with a version when the source states one. |
| Exemptions | `components[].identified` | `true` when a `bom.identify` rule in `scanoss.json` approved the component. |

### Adjust the licence pattern

`DENY_LICENSES` is a regular expression (jq `test`). The default `^(AGPL|GPL|SSPL)-` matches
`GPL-2.0-only`, `GPL-3.0-or-later`, `AGPL-3.0-only`, and `SSPL-1.0`, but not `LGPL-2.1-only`.
Some examples:

```bash theme={null}
# Only strong copyleft network licences
DENY_LICENSES='^(AGPL|SSPL)-' ci/scanoss-policy.sh scanoss-results.json

# An explicit list of IDs
DENY_LICENSES='^(GPL-3\.0-only|GPL-3\.0-or-later|AGPL-3\.0-only)$' ci/scanoss-policy.sh scanoss-results.json
```

Licence policy is a legal decision. Agree the pattern with whoever owns open source compliance in
your organisation, and keep it in the repository next to the script.

### Approved exceptions

If your organisation accepts a component on the deny-list for your project, approve it with a
`bom.identify` rule in `scanoss.json` and run the check with `SKIP_IDENTIFIED=true`. The
component then passes the licence rule, but the vulnerability rule still checks it. See
[Approve components with scanoss.json](/en/latest/developer-tools/recipes/approve-components-bom-rules).

## What the output means

```
Disallowed licences (pattern: ^(AGPL|GPL|SSPL)-):
pkg:github/example/readline@8.1  GPL-3.0-or-later
Vulnerabilities at or above 'high':
CVE-2022-37434  CRITICAL  pkg:github/madler/zlib@1.2.11
```

Each licence line names the component and the licences that matched the pattern. Each
vulnerability line names the advisory, its severity, and the affected components. To fix a
vulnerability, upgrade the component to a version without the advisory, and scan again.

## How it fails the pipeline

| Exit code | Meaning |
| - | - |
| `0` | No rule matched |
| `1` | The script could not run: a missing argument, an invalid `FAIL_ON_SEVERITY`, or a file `jq` cannot read |
| `2` | At least one licence or vulnerability matched |

If the scan before it fails, scanoss-cli exits `1` and the script never runs.

## Use it with an SBOM instead

If you start from a CycloneDX file rather than the raw inventory, the same checks read different
fields:

```bash theme={null}
# Licences
jq -r --arg deny '^(AGPL|GPL|SSPL)-' '
  .components[]
  | [.licenses[]?.license.id // empty | select(test($deny))] as $bad
  | select($bad | length > 0)
  | "\(.purl)  \($bad | join(", "))"' sbom.cdx.json

# Vulnerabilities rated critical or high
jq -r '
  .vulnerabilities[]?
  | select([.ratings[]?.severity] | any(. == "critical" or . == "high"))
  | "\(.id)  \([.affects[]?.ref] | join(", "))"' sbom.cdx.json
```

SPDX 2.3 has no vulnerability model, so only the licence rule applies to SPDX documents.


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