Skip to main content

TL;DR

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

Getting started

Let’s install the CLI and its extras.
  1. Install scanoss-py from PyPI:
pip install scanoss
On modern Linux distros with an externally managed Python (Ubuntu 23.04+, Fedora 38+, Debian 11+), use pipx install scanoss instead.
  1. Install scancode-toolkit to enable dependency scanning:
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.
  1. Store your API key in an environment variable so it never ends up hard-coded in a command or committed to a repo:
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.
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:
# 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:
# 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:
# 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:
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:
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:
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:
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 and the PAPI definitions.
EndpointMethodPurpose
/v2/vulnerabilities/componentGETKnown vulnerabilities for one component
/v2/vulnerabilities/componentsPOSTKnown vulnerabilities for many components
/v2/vulnerabilities/cpes/componentGETCPE identifiers for one component
/v2/vulnerabilities/cpes/componentsPOSTCPE identifiers for many components

Query one component’s vulnerabilities

Get known vulnerabilities for a single component, including CVE details, severity, and scoring data. Request
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
{
  "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:
{
  "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
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)
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
{
  "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:
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. EPSSpercentile 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:
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:
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.