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

# Vulnerability Scanning

> In this guide, you will learn how to scan a project for known security vulnerabilities (CVEs) using the [SCANOSS-PY](https://github.com/scanoss/scanoss.py) and the **SCANOSS API**.

## TL;DR

```bash theme={null}
# Install
pip install scanoss
pip install scancode-toolkit          # for --dependencies
export SCANOSS_API_KEY="..."          # required for vulnerability queries

# Phase 1 — Scan (code + deps)
scanoss-py scan --dependencies --output results.json --key "$SCANOSS_API_KEY" /path/to/your/project

# Phase 2 — Extract PURLs from BOTH surfaces (matched components + dependencies)
jq -r '.[][]
  | (select(.id=="file") | .version as $v | .purl[]? | "\(.)@\($v)"),
    (.dependencies[]?     | "\(.purl)@\(.version)")' results.json | sort -u > purls.txt
jq '{components: (
      [ .[][] | select(.id=="file") | .version as $v | .purl[]? | {purl: ., requirement: $v} ]
    + [ .[][] | .dependencies[]? | {purl, requirement} ]) | unique }' results.json > purl-list.json

# Phase 3a — Query via CLI
scanoss-py component vulnerabilities --purl "pkg:npm/lodash@4.17.20" --key "$SCANOSS_API_KEY"
scanoss-py component vulnerabilities --input purl-list.json --key "$SCANOSS_API_KEY" -o vulns.json

# Phase 3b — Query via API
curl -X GET "https://api.scanoss.com/v2/vulnerabilities/component?purl=pkg:npm/lodash&requirement=4.17.20" \
  -H "X-Api-Key: $SCANOSS_API_KEY" | jq
curl -X POST "https://api.scanoss.com/v2/vulnerabilities/components" \
  -H "Content-Type: application/json" -H "X-Api-Key: $SCANOSS_API_KEY" \
  -d '{"components":[{"purl":"pkg:npm/lodash","requirement":"4.17.20"}]}' | jq

# Component maintenance status
scanoss-py component status --purl "pkg:npm/lodash@4.17.20" --key "$SCANOSS_API_KEY"
```

By the end, you will have a workflow that finds vulnerabilities across **both surfaces**
SCANOSS detects, the open-source code matched inside your source files and the packages
your project declares as dependencies.

You can follow this end to end, or jump to [Using the REST API](#using-the-rest-api) if
you only want the REST endpoints.

## How SCANOSS detects vulnerabilities

Before building the workflow, it helps to understand the moving parts. Rather than relying solely on dependency manifests, SCANOSS fingerprints your source code locally and matches it against the SCANOSS Knowledge Base.
From those matches it derives components, and from components it derives vulnerabilities.

The important idea is that **vulnerabilities are attached to components (PURLs), not to
raw code**. And a scan surfaces components in **two distinct ways**, both of which carry
vulnerability risk:

1. **Matched components** — open-source code identified *inside your source files* by
   fingerprint (file and snippet matches). These represent code that was copied, vendored,
   or reused from an upstream project, and they appear as entries with `"id": "file"` and
   a top-level `purl` **array** (e.g. `pkg:github/miguelgrinberg/flasky`). A known CVE in
   that upstream project may well be present in the copied code.
2. **Declared dependencies** — packages your project pulls in through manifests
   (`requirements.txt`, `package.json`, `pom.xml`, …). These appear as entries with
   `"id": "dependency"` and a `dependencies` **array**, each element a package such as
   `pkg:pypi/flask`. This is the classic SCA surface.

Everything below produces PURLs from both surfaces, then queries those PURLs for CVEs.

## Prerequisites

This guide uses the following tools:

* [SCANOSS-PY](https://docs.scanoss.com/en/latest/cli/scanoss-py/installation)
* [`scancode-toolkit`](https://pypi.org/project/scancode-toolkit/)
* SCANOSS API Key

## Getting started

Let's install the CLI and its extras.

1. Install `scanoss-py` from PyPI:

```bash theme={null}
pip install scanoss
```

> On modern Linux distros with an externally managed Python (Ubuntu 23.04+, Fedora 38+, Debian 11+), use `pipx install scanoss` instead.

2. Install `scancode-toolkit` to enable dependency scanning:

```bash theme={null}
pip install scancode-toolkit
```

Without this package, the `--dependencies` flag will fail. Since the declared-dependency
surface is where many known CVEs live, installing it is strongly recommended for
vulnerability scanning.

3. Store your API key in an environment variable so it never ends up hard-coded in a command or committed to a repo:

```bash theme={null}
export SCANOSS_API_KEY="your-api-key-here"
```

You're now ready to scan.

## Step 1: Scan the project

The first phase identifies components. Let's scan a codebase **and** its declared
dependencies, writing structured results to a file.

```bash theme={null}
scanoss-py scan /path/to/your/project \
  --dependencies \
  --output results.json \
  --key "$SCANOSS_API_KEY"
```

This command:

1. Fingerprints your source files locally using the Winnowing algorithm.
2. Matches those fingerprints against the SCANOSS Knowledge Base.
3. Analyses declared dependencies from manifests (`package.json`, `requirements.txt`,
   `pom.xml`, …) when `--dependencies` is enabled.
4. Writes the complete results — detected components, versions, PURLs, and licences — to
   `results.json`.

A few variants you'll reach for:

```bash theme={null}
# Scan the current directory
scanoss-py scan --dependencies --output results.json --key "$SCANOSS_API_KEY" /path/to/project

# Analyse ONLY declared dependencies (skip file/snippet scanning). Faster, but note this
# DROPS the matched-component surface, so you lose CVEs in copied or vendored code.
scanoss-py scan --dependencies-only --output results.json /path/to/project

# Reuse a pre-generated fingerprint file (fingerprint once in CI, scan later)
scanoss-py scan --wfp project.wfp --output results.json --key "$SCANOSS_API_KEY"

# Apply a persistent settings file (skip patterns, declared components, etc.)
scanoss-py scan --settings scanoss.json --output results.json /path/to/project
```

The generated `results.json` is keyed by file path and contains the two entry types
described earlier — matched components (`"id": "file"`) and declared dependencies
(`"id": "dependency"`). The next step extracts PURLs from each.

## Step 2: Extract PURLs from both surfaces

Vulnerability queries operate on PURLs, so the second thing to do is pull them out of the
scan results. Because the two entry types have different shapes, you extract from each
with a slightly different filter, then combine them into one list to query together.

### Matched components

These are the `"id": "file"` entries. Their PURLs live in a top-level `purl` **array**,
and the matched version is the top-level `version`. Capture the version first, then emit
one line per PURL:

```bash theme={null}
# Matched-component PURLs, each paired with its matched version
jq -r '.[][] | select(.id=="file") | .version as $v | .purl[]? | "\(.)@\($v)"' results.json
```

### Declared dependencies

These are the `"id": "dependency"` entries. Their packages are nested in `dependencies[]`,
each with a string `purl`, `requirement`, and `version`:

```bash theme={null}
# Dependency PURLs, each paired with its version
jq -r '.[][] | .dependencies[]? | "\(.purl)@\(.version)"' results.json
```

### Combine both into one list

For a single vulnerability pass you want every PURL from both surfaces. One `jq`
invocation with two branches does it: the `select(.id=="file")` branch handles matched
components and the `.dependencies[]?` branch handles dependencies. For any given object
only one branch produces output, so there's no cross-contamination:

```bash theme={null}
jq -r '
  .[][]
  | (select(.id=="file") | .version as $v | .purl[]? | "\(.)@\($v)"),
    (.dependencies[]?     | "\(.purl)@\(.version)")
' results.json | sort -u > purls.txt
```

The same two branches build the batch query payload, mapping each source to the
`{purl, requirement}` shape the query expects. Matched components use their matched
`version` as the requirement; dependencies use their declared `requirement`:

```bash theme={null}
jq '{components: (
      [ .[][] | select(.id=="file") | .version as $v | .purl[]? | {purl: ., requirement: $v} ]
    + [ .[][] | .dependencies[]? | {purl, requirement} ]
    ) | unique }' results.json > purl-list.json
```

## Step 3: Query vulnerabilities

With PURLs in hand, the third phase resolves them to CVEs. There are two ways to do this:
the **CLI**, which is fastest for local and scripted use, and the **REST API**, which is
the integration point for other services and languages. Both take the same PURLs in and
return the same vulnerability objects out.

### Using the CLI

The `component vulnerabilities` command (alias `comp vulns`) queries the Knowledge Base. It requires
an API key.

Query a single component:

```bash theme={null}
scanoss-py component vulnerabilities \
  --purl "pkg:npm/lodash@4.17.20" \
  --key "$SCANOSS_API_KEY"
```

Or use the batch form, reading the input file from Step 2. Add `--output` (`-o`) to save
the results to a file instead of printing to STDOUT:

```bash theme={null}
scanoss-py component vulnerabilities \
  --input purl-list.json \
  --key "$SCANOSS_API_KEY" \
  --output vulns.json
```

The response lists each component with a `vulnerabilities` array.

### Using the REST API

The CLI is a convenience layer over the SCANOSS REST API. Calling the API directly is
useful when you're integrating from a language without the SDK, debugging with `--trace`,
or wiring vulnerability data into another service.

Every request goes to `https://api.scanoss.com` and authenticates with your API key in the
`X-Api-Key` header (reuse the `$SCANOSS_API_KEY` you exported earlier). There are two
resource types (vulnerabilities and CPEs) and two shapes for each, a single component via
`GET`, or many components via `POST`:

For the complete API reference, including request and response definitions, see the
[Vulnerability API documentation](https://docs.scanoss.com/en/latest/apis/vulnerability-api) and the
[PAPI definitions](https://github.com/scanoss/papi/blob/main/protobuf/scanoss/api/vulnerabilities/v2/README.md).

| Endpoint                              | Method | Purpose                                       |
| ------------------------------------- | ------ | --------------------------------------------- |
| `/v2/vulnerabilities/component`       | `GET`  | Known vulnerabilities for **one** component   |
| `/v2/vulnerabilities/components`      | `POST` | Known vulnerabilities for **many** components |
| `/v2/vulnerabilities/cpes/component`  | `GET`  | CPE identifiers for **one** component         |
| `/v2/vulnerabilities/cpes/components` | `POST` | CPE identifiers for **many** components       |

#### Query one component's vulnerabilities

Get known vulnerabilities for a single component, including CVE details, severity, and
scoring data.

**Request**

```bash theme={null}
curl -X GET 'https://api.scanoss.com/v2/vulnerabilities/component?purl=pkg:github/scanoss/engine&requirement=>=5.0.0' \
  -H "X-Api-Key: $SCANOSS_API_KEY" | jq
```

**Response fields**

The response returns:

* `purl` — the requested component
* `version` — the specific version that was analysed
* `requirement` — echoes the version constraint from the request
* `vulnerabilities` — the list of known vulnerabilities affecting the component

Each vulnerability object contains a CVE identifier and reference URL, a severity
classification, publication and modification dates, a summary, the source database, a
`cvss` scoring array, and Exploit Prediction Scoring System (EPSS) data.

The `cvss` field is an array of CVSS objects (allowing multiple versions or sources), each
containing:

* `cvss` — the CVSS vector string (e.g. `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`)
* `cvss_score` — the numerical score (0.0 to 10.0)
* `cvss_severity` — the severity rating derived from the score (`None`, `Low`, `Medium`, `High`, `Critical`)

**Response — component with vulnerabilities**

```json theme={null}
{
  "component": {
    "purl": "pkg:github/scanoss/engine",
    "requirement": ">=5.0.0",
    "version": "5.0.0",
    "vulnerabilities": [
      {
        "id": "CVE-2024-12345",
        "cve": "CVE-2024-12345",
        "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-12345",
        "summary": "Buffer overflow vulnerability in input processing",
        "severity": "High",
        "published": "2024-01-15T10:30:00Z",
        "modified": "2024-01-16T14:20:00Z",
        "source": "NVD",
        "cvss": [
          {
            "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
            "cvss_score": 7.5,
            "cvss_severity": "High"
          }
        ],
        "epss": {
          "probability": 0.00053,
          "percentile": 0.16477
        }
      }
    ]
  },
  "status": {
    "status": "SUCCESS",
    "message": "Vulnerabilities Successfully retrieved"
  }
}
```

**Response — no known vulnerabilities**

An empty array is a success, not an error:

```json theme={null}
{
  "component": {
    "purl": "pkg:github/scanoss/scanoss.py",
    "requirement": ">1.30.0",
    "version": "1.31.0",
    "vulnerabilities": []
  },
  "status": {
    "status": "SUCCESS",
    "message": "Vulnerabilities Successfully retrieved"
  }
}
```

#### Query many components' vulnerabilities

Get known vulnerabilities for multiple components in a single request. This is the
endpoint the batch `comp vulns -i purl-list.json` command calls under the hood.

**Request**

```bash theme={null}
curl -X POST 'https://api.scanoss.com/v2/vulnerabilities/components' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $SCANOSS_API_KEY" \
  -d '{
    "components": [
      {"purl": "pkg:github/scanoss/engine", "requirement": ">=5.0.0"},
      {"purl": "pkg:github/scanoss/scanoss.py", "requirement": "~1.30.0"}
    ]
  }' | jq
```

The response mirrors the single-component shape, wrapped in a `components` array — one
entry per requested PURL, each with its own `vulnerabilities` list.

#### Query CPE identifiers

CPEs (Common Platform Enumeration) are the identifiers vulnerability databases key on.
Retrieving them is useful when you want to cross-reference a component against an external
CVE feed yourself.

**Request (single component)**

```bash theme={null}
curl -X GET 'https://api.scanoss.com/v2/vulnerabilities/cpes/component?purl=pkg:github/scanoss/engine&requirement=>=5.0.0' \
  -H "X-Api-Key: $SCANOSS_API_KEY" | jq
```

**Response**

```json theme={null}
{
  "component": {
    "purl": "pkg:github/scanoss/engine",
    "requirement": ">=5.0.0",
    "version": "5.0.0",
    "cpes": ["cpe:2.3:a:scanoss:engine:1.0.0:*:*:*:*:*:*:*"]
  },
  "status": {
    "status": "SUCCESS",
    "message": "CPEs Successfully retrieved"
  }
}
```

**Request (multiple components)**

The batch form takes the same `components` array as the vulnerabilities POST:

```bash theme={null}
curl -X POST 'https://api.scanoss.com/v2/vulnerabilities/cpes/components' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $SCANOSS_API_KEY" \
  -d '{
    "components": [
      {"purl": "pkg:github/scanoss/engine", "requirement": ">=5.0.0"},
      {"purl": "pkg:github/scanoss/scanoss.py", "requirement": "~1.30.0"}
    ]
  }' | jq
```

## Step 4: Interpret and prioritise the findings

Raw CVE lists aren't a plan. For each finding, weigh four signals together:

1. **Severity** — the coarse label (`HIGH` / `CRITICAL` first).
2. **CVSS** — the numeric `cvss_score` and its vector give a precise, comparable measure
   of impact and exploitability.
3. **EPSS** — `percentile` estimates the *likelihood of exploitation in the wild*. Two
   HIGH CVEs with very different EPSS percentiles deserve very different urgency.
4. **Fix availability** — the `summary` typically names the fixed version
   (e.g. "prior to 4.17.21"), which is your remediation target. Compare the component's
   `version` against `latest` from the scan to gauge how big the upgrade is.

It's also worth keeping the two surfaces labelled: a CVE in a **matched component** means
*code you copied* carries the flaw, while a CVE in a **dependency** means *a package you
install* does — the remediation differs (patch or replace the copied code vs. bump the
dependency).

A quick way to surface only the findings that matter, from a saved query response:

```bash theme={null}
jq '.components[].vulnerabilities[]?
    | select(.severity | test("HIGH|CRITICAL"; "i"))
    | { cve, severity, source, url }' vulns.json
```

You can also check whether a component is simply unmaintained, a latent risk even without
a current CVE, with `component status`:

```bash theme={null}
scanoss-py component status \
  --purl "pkg:npm/lodash@4.17.20" \
  --key "$SCANOSS_API_KEY"
```

## Putting it all together

A full vulnerability scan flows like this:

1. **Scan** (`scanoss-py scan --dependencies`) fingerprints the project and matches it
   against the Knowledge Base, producing `results.json` with both surfaces: matched
   components (`"id": "file"`) and declared dependencies (`"id": "dependency"`).
2. **Extract** (`jq`) pulls `purl@version` pairs from both surfaces — the top-level
   `purl[]` on matched components and the nested `dependencies[]` on dependency entries —
   and merges them into one list.
3. **Query** — either `comp vulns` from the CLI or a `POST /v2/vulnerabilities/components`
   call to the API — resolves each PURL to its known CVEs, with severity, CVSS, and EPSS.
4. **Interpret** ranks the findings by severity, CVSS, and EPSS, keeps the two surfaces
   labelled, and reads the fix version out of each summary.
