# SBOM Vulnerability Scanner — Lockfile CVE Scan (OSV/KEV) (`malonestar/sbom-vulnerability-rollup`) Actor

SBOM vulnerability scanner: paste an npm, pip or go lockfile and get a prioritized CVE report with upgrade targets. Batch-queries OSV.dev, flags CISA KEV known-exploited vulns, adds NVD CVSS, and computes the minimum fix version per package plus a severity rollup. Keyless.

- **URL**: https://apify.com/malonestar/sbom-vulnerability-rollup.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Developer tools, AI, Agents
- **Stats:** 2 total users, 0 monthly users, 93.1% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $11.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## SBOM Vulnerability Scanner — Lockfile CVE Scan & Upgrade Targets (OSV/NVD/KEV)

Paste a dependency **lockfile** and get a prioritized **CVE / vulnerability
report with upgrade targets**. This **SBOM vulnerability scanner** batch-queries
**OSV.dev**, flags **CISA KEV** known-exploited vulnerabilities with their
**BOD 22-01 remediation due dates**, computes a **CVSS base score** from the
advisory's own CVSS vector, and works out the **minimum fix version** per
package plus a severity rollup. Existing OSV actors echo raw records — this one
does the transform that is the value.

### Who it's for

- **DevSecOps & AppSec** engineers scanning dependencies in CI.
- **Security & compliance** teams producing SBOM vulnerability reports and
  tracking KEV remediation deadlines.
- **Platform / SRE** teams prioritizing which upgrades actually matter.
- **AI coding agents** needing a "scan this lockfile for CVEs" tool.

### What it does

1. **Parses a manifest** into `{package, version, ecosystem}` — `package-lock.json`
   / `package.json` (npm), `requirements.txt` (PyPI), or `go.mod` (Go), or a
   direct `dependencies` array.
2. **Runs a live drift check before anything is billed** — a known-vulnerable
   canary that must return findings and a known-clean control that must return
   none. If OSV has stopped matching, the run fails loudly instead of publishing
   a false clean bill of health.
3. **Batch-queries OSV.dev** for known vulnerabilities and full advisory bodies,
   reading `next_page_token` so a heavily-affected package is never truncated.
4. **Enriches** each finding with the **CISA KEV** record (exploited flag,
   remediation due date, ransomware linkage, required action) and, optionally,
   **NVD CVSS**.
5. **Computes upgrade targets** — the minimum fix version per package — plus a
   severity rollup (critical / high / medium / low, KEV-exploited, fixable).

### How a failure is reported — read this before you trust a clean result

A security scanner that answers "no vulnerabilities" when a source was
unreachable is worse than one that crashes. This actor uses an explicit
three-state contract on every row:

| Value | Meaning |
|---|---|
| `true` | Checked, and the answer is yes. |
| `false` | **Checked against a healthy source**, and the answer is no. |
| `null` | **Not checked.** The source did not answer. Never read this as "no". |

- Every row carries `osv_detail_status`, `kev_status`, `nvd_status` and
  `row_partial`.
- If the CISA KEV catalog is unreachable, `kev_exploited` is `null` on every
  row — it is **not** reported as `false`.
- If an advisory body cannot be retrieved, `fix_available` is `null` and
  `fix_status` is `"unknown"` — distinct from `"no_fix_published"`, which means
  the advisory *was* read and publishes no fix.
- The summary row carries `results_partial` and a plain-language
  `partial_reason`.
- If OSV cannot be reached, if the drift canary fails, if the KEV catalog is
  stale or truncated, or if **every** advisory fetch fails, the run **fails**
  and emits nothing — so nothing is billed.

### Example input

```json
{
  "dependencies": [
    { "name": "lodash", "version": "4.17.15", "ecosystem": "npm" },
    { "name": "org.apache.logging.log4j:log4j-core", "version": "2.14.1", "ecosystem": "Maven" }
  ],
  "includeNvd": false,
  "maxPackages": 2000
}
```

Or paste a manifest:

```json
{
  "manifestContent": "<paste your package-lock.json, requirements.txt or go.mod here>",
  "manifestFormat": "auto",
  "includeNvd": false,
  "maxPackages": 2000
}
```

**Ecosystems:** `npm`, `PyPI`, `Go`, `Maven`, `RubyGems`, `crates.io`, `NuGet`,
`Packagist`. Maven names must be the full `groupId:artifactId` coordinate; Go
uses the full module path.

### Output fields

Two row types share one dataset. `record_type` is `"summary"` or
`"vulnerability"`.

#### Vulnerability rows

| Field | Description |
|---|---|
| `record_type` | `"vulnerability"`. |
| `package` | Package name as queried. |
| `ecosystem` | OSV ecosystem (`npm`, `PyPI`, `Go`, `Maven`, ...). |
| `installed_version` | The version found in your manifest. |
| `dependency_type` | `direct` or `transitive`. |
| `vulnerability_id` | OSV advisory id, e.g. `GHSA-jfh8-c2jp-5v3q`. |
| `aliases` | CVE aliases only (unchanged since v1.0). |
| `all_aliases` | Every alias OSV publishes, including GHSA / PYSEC / GO / RUSTSEC. |
| `related_ids` | Advisories OSV marks as related. |
| `advisory_source` | Issuing database, from the id prefix (`GHSA`, `PYSEC`, `GO`, ...). |
| `summary` | Short advisory summary (300 chars). |
| `details` | Full advisory description. |
| `severity` | `CRITICAL` / `HIGH` / `MEDIUM` / `LOW`, or `null` if not established. |
| `severity_source` | `nvd_score`, `nvd_text`, `osv_database_specific`, or `osv_vector`. |
| `cvss_score` | CVSS **base** score. Populated without NVD since v1.1. |
| `cvss_vector` | The vector it was scored from. |
| `cvss_version` | `3.1`, `3.0`, `4.0` or `2.0`. |
| `cvss_source` | `nvd`, `osv_vector`, or `osv_numeric`. |
| `kev_exploited` | `true` / `false` / `null`. `null` means the KEV catalog was unavailable. |
| `kev_vulnerability_name` | CISA KEV catalog name. |
| `kev_vendor_project` | CISA KEV vendor or project. |
| `kev_product` | CISA KEV product. |
| `kev_date_added` | Date CISA added the CVE to KEV. |
| `kev_due_date` | **BOD 22-01 federal remediation deadline.** |
| `kev_required_action` | The action CISA requires. |
| `kev_short_description` | CISA description of the exploited vulnerability. |
| `kev_known_ransomware` | `true` when CISA records ransomware-campaign use. |
| `kev_notes` | CISA notes / reference URLs. |
| `kev_cwes` | CWEs CISA associates with the KEV entry. |
| `fixed_version` | **Upgrade target** — the minimum fixed version above yours. |
| `fix_available` | `true` / `false` / `null` (advisory not read). |
| `fix_status` | `fix_available`, `no_fix_published`, or `unknown`. |
| `fix_references` | OSV references typed `FIX` (remediating commits/releases). |
| `advisory_references` | OSV references typed `ADVISORY`. |
| `references` | All reference URLs, flat (unchanged since v1.0). |
| `introduced_version` | Earliest affected version. |
| `last_affected_version` | Highest version still affected, when no fix is published. |
| `affected_versions_listed` | Count of explicitly enumerated affected versions. |
| `package_purl` | Package URL coordinate from OSV. |
| `cwe_ids` | CWE identifiers from the advisory. |
| `published` / `modified` | Advisory timestamps. |
| `withdrawn` | ISO timestamp if the advisory was withdrawn, else `null`. |
| `is_withdrawn` | `true` / `false` / `null`. A withdrawn advisory should not drive remediation. |
| `osv_url` | Link to the advisory on osv.dev. |
| `osv_detail_status` | `ok` or `unavailable`. |
| `osv_detail_error` | The transport error, when the advisory could not be read. |
| `kev_status` | `ok` or `unavailable`. |
| `nvd_status` | `ok`, `not_requested`, `unavailable`, or `rate_limited`. |
| `row_partial` | `true` when any contributing source did not answer for this row. |

#### Summary row

| Field | Description |
|---|---|
| `record_type` | `"summary"`. |
| `total_vulnerabilities` | Findings emitted. |
| `affected_packages` | Distinct packages with at least one finding. |
| `critical` / `high` / `medium` / `low` / `unknown` | Severity buckets. |
| `kev_exploited_count` | Findings confirmed in the CISA KEV catalog. |
| `kev_status_unknown_count` | Findings whose exploitation status **could not be checked**. Not counted as "not exploited". |
| `kev_ransomware_count` | Findings CISA links to ransomware campaigns. |
| `kev_overdue_count` | Findings past their BOD 22-01 due date. |
| `fixable_count` | Findings with a computed upgrade target. |
| `fix_status_unknown_count` | Findings whose fix availability could not be established. |
| `max_cvss_score` | Highest CVSS base score in the finding set. |
| `withdrawn_count` | Findings whose advisory has been withdrawn. |
| `advisories_without_detail` | Advisory bodies that could not be retrieved. |
| `packages_scanned` | **How many packages the verdict actually covers.** |
| `packages_submitted` | Packages the input produced, before the `maxPackages` cap. |
| `packages_truncated` | Packages dropped by the cap and therefore **not** scanned. |
| `packages_clean` | Scanned packages with no known vulnerability. |
| `input_source` | `manifest` or `dependencies`. |
| `manifest_format_detected` | The format actually used, so a mis-detected paste is visible. |
| `osv_status` / `kev_status` / `nvd_status` | Per-source outcome for the run. |
| `kev_catalog_version` | CISA KEV `catalogVersion` used (e.g. `2026.07.29`). |
| `kev_catalog_date` | CISA KEV `dateReleased`. The run fails if this is more than 30 days old. |
| `kev_catalog_count` | Entries in the KEV catalog used (1656 on 2026-08-01). |
| `results_partial` | `true` when anything was incomplete. |
| `partial_reason` | Plain-language description of exactly what was incomplete. |
| `scan_completed_at` | ISO timestamp, so a stored result carries its own vintage. |

**Populating the optional columns.** `kev_*` columns populate only for findings
whose CVE is in the KEV catalog — the prefill input
(`log4j-core@2.14.1` + `lodash@4.17.15`) exercises this, since CVE-2021-44228 is
in KEV. `nvd_status` becomes `ok` only when `includeNvd` is `true`.
`last_affected_version` populates for advisories that publish `last_affected`
instead of a fix. `partial_reason` and `osv_detail_error` are `null` on a
healthy run — that is good news, not a dead column.

### Use as an MCP tool

Exposed to AI coding agents via `mcp.apify.com` as a "scan lockfile for
vulnerabilities" tool — paste a lockfile, get a prioritized fix list back. The
three-state `null` / `false` / `true` contract matters especially here: an agent
must not read `kev_exploited: null` as "safe".

### FAQ

**Which ecosystems?** Manifest parsing covers npm, PyPI (pip) and Go; the direct
`dependencies` array covers every OSV ecosystem including Maven, RubyGems,
crates.io, NuGet and Packagist.

**Do I need NVD enrichment?** No. Since v1.1 the CVSS base score is computed
from the CVSS vector OSV already ships, verified against NVD on 20 of 20 v3.1 /
v3.0 / v2.0 vectors. `includeNvd` remains available for the authoritative NVD
number, and is budgeted and throttled because NVD's keyless quota is 5 requests
per 30 seconds.

**What is the upgrade target?** The minimum fixed version that is an upgrade
from the version you have.

**Does a clean manifest cost much?** No — it emits only the summary row, which
now also tells you how many packages that clean result covers.

**Can a clean result be wrong?** It can be *incomplete*, and the actor says so:
check `results_partial`, `packages_truncated` and the `*_status` fields. If a
source failed outright the run fails and bills nothing.

**Is this a determination?** No. It is a screening tool over public advisory
data. Reachability, runtime configuration and vendor backports are not modelled.

### Data sources (all keyless, no anti-bot)

- **OSV.dev** — `POST /v1/querybatch`, `GET /v1/vulns/{id}`, `POST /v1/query`
  for pagination.
- **CISA KEV** — the Known Exploited Vulnerabilities catalog JSON feed.
- **NVD** — CVSS 2.0 API, optional, budgeted and throttled.

### Pricing (Pay Per Result)

Billed per dataset record returned: one summary row plus one row per finding. A
clean manifest emits only the summary row. Runs that fail a drift assertion or
lose every source emit nothing and bill nothing.

### Related actors

- **npm Package Health Scorer** — score the maintenance risk of the packages
  this scan flags (abandoned, single-maintainer, deprecated).
- **Site Due Diligence Bundle** and the environmental screeners — different
  domain, same discipline: per-source status on every row.

# Actor input Schema

## `manifestContent` (type: `string`):

Paste the full text of a dependency manifest to scan: an npm package-lock.json (or package.json), a Python requirements.txt, or a Go go.mod. The actor parses it into {package, version, ecosystem} tuples and queries OSV.dev. Leave blank if you supply the 'Dependencies' array instead.

## `manifestFormat` (type: `string`):

Format of the pasted manifest. 'auto' detects it from the content (JSON -> npm, name==version lines -> pip, module/require -> go). Set explicitly to override detection.

## `dependencies` (type: `array`):

Optional explicit list of dependencies to scan, bypassing manifest parsing. Each item is {"name","version","ecosystem"}. Ecosystems: npm, PyPI, Go, Maven, RubyGems, crates.io, NuGet, Packagist. IMPORTANT: Maven names must be the full "groupId:artifactId" coordinate (e.g. "org.apache.logging.log4j:log4j-core", not "log4j-core"); Go uses the full module path. Takes precedence over 'Manifest content' when non-empty.

## `includeNvd` (type: `boolean`):

When enabled, look up an NVD CVSS base score per distinct CVE. NVD is keyless but hard-capped at 5 requests / 30 s, so this is budgeted and throttled and each row reports nvd\_status. Leave OFF for most runs: since v1.1 the CVSS base score is computed directly from the CVSS vector OSV already publishes, so cvss\_score is populated either way.

## `maxPackages` (type: `integer`):

Maximum number of dependency tuples to query against OSV. Larger manifests are truncated to this cap to bound run time and cost.

## `maxNvdLookups` (type: `integer`):

Hard budget on how many distinct CVEs are sent to NVD when NVD enrichment is on. NVD answers HTTP 429 beyond roughly 5 requests per 30 seconds, so a high budget makes the run slow rather than more complete. CVEs over budget still carry the CVSS score computed from the OSV vector.

## Actor input object example

```json
{
  "manifestFormat": "auto",
  "dependencies": [
    {
      "name": "lodash",
      "version": "4.17.15",
      "ecosystem": "npm"
    },
    {
      "name": "org.apache.logging.log4j:log4j-core",
      "version": "2.14.1",
      "ecosystem": "Maven"
    }
  ],
  "includeNvd": false,
  "maxPackages": 2000,
  "maxNvdLookups": 25
}
```

# Actor output Schema

## `results` (type: `string`):

Per-package vulnerability findings with upgrade targets, KEV flags, and a severity rollup.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "dependencies": [
        {
            "name": "lodash",
            "version": "4.17.15",
            "ecosystem": "npm"
        },
        {
            "name": "org.apache.logging.log4j:log4j-core",
            "version": "2.14.1",
            "ecosystem": "Maven"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/sbom-vulnerability-rollup").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "dependencies": [
        {
            "name": "lodash",
            "version": "4.17.15",
            "ecosystem": "npm",
        },
        {
            "name": "org.apache.logging.log4j:log4j-core",
            "version": "2.14.1",
            "ecosystem": "Maven",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("malonestar/sbom-vulnerability-rollup").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "dependencies": [
    {
      "name": "lodash",
      "version": "4.17.15",
      "ecosystem": "npm"
    },
    {
      "name": "org.apache.logging.log4j:log4j-core",
      "version": "2.14.1",
      "ecosystem": "Maven"
    }
  ]
}' |
apify call malonestar/sbom-vulnerability-rollup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=malonestar/sbom-vulnerability-rollup",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/j94MapwyVsP72uVwS/builds/g0WpfK10IuOPzMj7g/openapi.json
