# OSV Package Advisory Monitor (`maydit/osv-package-advisory-monitor`) Actor

Monitor known OSV advisories for explicit package versions. Compare affected ranges, fix events and source corrections, with bounded checks of previously saved IDs for explicit withdrawals.

- **URL**: https://apify.com/maydit/osv-package-advisory-monitor.md
- **Developed by:** [Brandt May](https://apify.com/maydit) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 package advisory observations

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

![OSV Package Advisory Monitor — illustrative watchlist workflow](https://api.apify.com/v2/key-value-stores/cCyrofyNNh9lwffXB/records/osv-package-advisory-monitor-a6b1ba75260e.png?signature=8mxDG7KwZPOaIIZwPt0W)

### Keep a reviewable package advisory watchlist

Give this Actor the package versions your workflow tracks. It queries the official OSV API and exports source advisory IDs, affected ranges, published fix events and saved corrections. Repeated comparisons also check previously saved IDs for explicit withdrawal evidence.

**One result is one package/version plus advisory observation.** A match reports OSV's evidence; it does not prove your running application is exploitable, inspect installed code or certify a version as safe.

[First run](#try-a-small-watchlist) · [Output preview](#output-preview) · [Saved comparisons](#save-a-comparison-baseline) · [Pricing](#pricing)

### Try a small watchlist

Paste this exact input into Console's JSON editor. No source API key is required. These historical examples demonstrate the fields; they are not recommendations to install these versions.

```json
{
  "packages": [
    {
      "ecosystem": "npm",
      "name": "lodash",
      "version": "4.17.20"
    }
  ],
  "maxResults": 5,
  "maxAdvisoriesPerPackage": 20
}
```

Open **Dataset** for saved observations and **Run report** for coverage. **SUMMARY** is the machine-readable record. Empty API input and the Console prefill use the documented sample inventory; the maximum is a cap, not a promised result count.

### Output preview

| Review question | Fields | Interpretation |
| --- | --- | --- |
| Which inventory entry? | `watchKey`, `ecosystem`, `packageName`, `requestedVersion` | Stable identity of the exact normalized package/version query |
| Which source advisory? | `advisoryId`, `aliases`, `advisoryUrl` | Source IDs remain separate; aliases do not prove two records should be merged |
| Why was it returned? | `queryMatched`, `watchStatus`, `matchingAffected` | OSV query match or a checked previously saved ID; not code-level reachability proof |
| What versions are mentioned? | `fixedVersionEvents`, `matchingAffected` | Publisher fix events and range evidence, not an upgrade recommendation or a universally safe version |
| Was it withdrawn? | `withdrawnAt`, `withdrawalNewlyObserved` | Explicit publisher withdrawal timestamp, never an inference from absence |
| What changed? | `changedFields`, `previous`, `addedAliases`, `addedFixedVersionEvents` | Observed record corrections against the last confirmed saved row |
| Where and when? | `sourceUrl`, `publishedAt`, `modifiedAt`, `observedAt` | API evidence, publisher timestamps and retrieval time |

`watchStatus` is `query_match`, `withdrawn` or `not_in_current_query`. The last value means a previously saved ID was retrieved successfully but was absent from a completed current query. It does **not** mean the package is fixed. Raw source severity vectors are preserved in `severity`; no severity score or exploit prediction is invented. Empty optional arrays and null optional fields are honest missing source metadata.

### Inspect a second inventory

Inspect known OSV advisory records matching a specific Maven Log4j version, with publisher ranges and fix events.

```json
{
  "packages": [
    {
      "ecosystem": "Maven",
      "name": "org.apache.logging.log4j:log4j-core",
      "version": "2.14.1"
    }
  ],
  "maxResults": 5,
  "maxAdvisoriesPerPackage": 20
}
```

### Save a comparison baseline

Save current lodash advisory evidence and compare later source corrections, ranges and explicit withdrawals.

```json
{
  "packages": [
    {
      "ecosystem": "npm",
      "name": "lodash",
      "version": "4.17.20"
    }
  ],
  "comparePrevious": true,
  "snapshotName": "lodash-advisory-review",
  "maxResults": 50,
  "maxAdvisoriesPerPackage": 20
}
```

1. Run once to save `new` observations.
2. Repeat with the same inventory, `snapshotName` and named history store. Rows become `changed` or `unchanged`, with `previous`, `previousObservedAt` and `changedFields`.
3. Add `onlyChanges: true` to suppress unchanged source observations. This also enables comparison. A healthy repeat can save zero rows.
4. In Sheets, Make or n8n, wait for completion, check SUMMARY, then upsert by `observationId`. Keep the run ID for replay and deduplicate deliveries with `observationId + contentHash`.

The versioned history scope contains the exact sorted inventory and namespace. Changing that inventory starts a separate comparison. Only confirmed dataset writes advance history. Errors, unsaved capped rows and unchanged skips preserve prior observations; missing sources never imply deletion. Use **one writer per scope**: a pre-commit check detects existing concurrent changes, but is not an atomic lock. The named store holds at most 20,000 saved identities per scope.

The Actor supplies observations and a report. You configure schedules, alerts and downstream accounts. A history failure can leave valid saved rows; inspect `historyUpdated` and `failedSources.history`. Comparisons cover last saved observations, not every upstream change between runs.

### Coverage and interpretation

- **Exact inventory:** up to 20 package/version entries from npm, PyPI, Maven, Go, crates.io or RubyGems. PyPI names use normalized lowercase hyphen spelling. No repository cloning, transitive dependency resolution, lockfile parsing or installed-code inspection.
- **Publisher matching:** OSV performs version matching, documented as fuzzy upstream matching. Returned records must have matching package evidence. A successful no-match query saves zero rows and does not certify safety.
- **Bounded results:** default 20 current advisories and three query pages per package; maximum 100 and ten. Per-package source truncation is marked incomplete. The separate `maxResults` output cap is an intentional sample and can leave selected output or packages unreached.
- **Withdrawal checks:** OSV excludes withdrawn records from query results but retains individual records by ID. Comparison runs check up to 100 absent, previously saved IDs per package after current rows. Incomplete pagination, output limits, unavailable IDs or deadlines leave explicit coverage gaps. No missing ID becomes a removal/resolution event.
- **Evidence only:** no exploit instructions, security certification, installation, remediation action or automatic upgrade. The official free OSV-Scanner is a strong option for repository and SBOM scanning.
- **Access and budgets:** public documented APIs only, sequential requests, pinned public DNS and no cross-host redirect following. Responses are capped at 2 MiB. One bounded retry is allowed for 502/503/504; rate/access refusals are reported without bypass.
- **Run bounds:** `maxRunSeconds` defaults to 240 and also respects the platform deadline minus 25 seconds. Configure Apify's maximum total charge as well as `maxResults`.

SUMMARY includes saved/observed counts, per-source coverage, unreached entries, history outcome and time/spending limits. Intentional output caps are bounded samples. Source, source-window, time and history failures are partial or failed. All-source or all-output failures fail the run. Errors and no-match diagnostics are never paid data rows.

### Pricing

Base FREE-tier rate: **$2 per 1,000 saved observations**, plus the listed **Actor Start** charge. The [Pricing tab](https://apify.com/maydit/osv-package-advisory-monitor/pricing) is authoritative for your plan. Saved unchanged observations are billable; unchanged rows suppressed by `onlyChanges` have no dataset-item charge. Platform and storage charges may apply under your plan.

### Sources and support

[OSV query API](https://google.github.io/osv.dev/post-v1-query/) · [Individual record API](https://google.github.io/osv.dev/get-v1-vulns/) · [Withdrawal and source behavior](https://google.github.io/osv.dev/faq/) · [OSV schema](https://ossf.github.io/osv-schema/) · [OSV-Scanner](https://google.github.io/osv-scanner/)

OSV aggregates records from multiple publishers. Retain the advisory and reference URLs when using exported evidence. This community Actor is not affiliated with Google, OSV or the upstream advisory publishers.

For help, open **Issues** with the run URL and expected public field, or contact **bmay@mayd-it.com**. Remove credentials and private inventory information before posting.

# Actor input Schema

## `packages` (type: `array`):

Up to 20 objects containing ecosystem, name and one explicit version. Supported ecosystems: npm, PyPI, Maven, Go, crates.io and RubyGems. Omitted or empty uses lodash 4.17.20 as a historical sample. No credentials, repository cloning or version-range inputs.

## `maxResults` (type: `integer`):

Caps confirmed dataset rows, including saved unchanged observations. A limit is a sample, not a complete inventory; inspect sourceCoverage and sourcesNotReached in SUMMARY.

## `comparePrevious` (type: `boolean`):

First observations are new. Later rows include changed/unchanged status, prior saved values and changedFields. Only confirmed saved rows advance history.

## `onlyChanges` (type: `boolean`):

Implies comparison. A healthy unchanged or no-match run can save zero rows. Missing advisory IDs never imply remediation.

## `snapshotName` (type: `string`):

1-80 letters, numbers, underscores or hyphens. History also includes the exact sorted inventory. Use one writer per scope; changing the inventory starts a separate baseline.

## `stateStoreName` (type: `string`):

3-63 letters, numbers or hyphens. Accessed only for comparisons. Use a dedicated store for each customer workflow.

## `maxAdvisoriesPerPackage` (type: `integer`):

Selects current query matches in advisory-ID order. A smaller bound explicitly reports partial source coverage. Previously saved absent IDs are checked separately, up to 100 per package.

## `maxPagesPerPackage` (type: `integer`):

Continue publisher pagination, including token-only pages, until complete or this bound. Repeated tokens and malformed responses fail explicitly.

## `maxRunSeconds` (type: `integer`):

Includes source and comparison work; also bounded by the platform deadline minus 25 seconds. Each response is capped to 2 MiB. Access and rate refusals are reported.

## Actor input object example

```json
{
  "packages": [
    {
      "ecosystem": "npm",
      "name": "lodash",
      "version": "4.17.20"
    }
  ],
  "maxResults": 5,
  "comparePrevious": false,
  "onlyChanges": false,
  "snapshotName": "default",
  "stateStoreName": "maydit-osv-package-watchlists",
  "maxAdvisoriesPerPackage": 20,
  "maxPagesPerPackage": 3,
  "maxRunSeconds": 240
}
```

# Actor output Schema

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

Source-backed observations only. Errors and empty-match diagnostics never become paid dataset rows.

## `runReport` (type: `string`):

Readable coverage, limits and saved-history outcome.

## `summary` (type: `string`):

Saved counts, source coverage, incomplete checks, failed sources and stop flags.

## `csvExport` (type: `string`):

Download saved observations for a spreadsheet; JSON retains nested evidence.

# 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 = {
    "packages": [
        {
            "ecosystem": "npm",
            "name": "lodash",
            "version": "4.17.20"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maydit/osv-package-advisory-monitor").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 = { "packages": [{
            "ecosystem": "npm",
            "name": "lodash",
            "version": "4.17.20",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("maydit/osv-package-advisory-monitor").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "packages": [
    {
      "ecosystem": "npm",
      "name": "lodash",
      "version": "4.17.20"
    }
  ]
}' |
apify call maydit/osv-package-advisory-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maydit/osv-package-advisory-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/3pNiCtjjN3zudBnwn/builds/qAIiKXb4ftOozFwVf/openapi.json
