# Dependency Health Checker: npm & PyPI Vulnerabilities (`m_ctim/package-health-checker`) Actor

Paste a package.json or requirements.txt, or list packages, and get one clean row per dependency: latest version, how far behind, deprecated or yanked, known vulnerabilities from OSV with fix versions, license, and a documented 0-100 health score. Built to be called by AI coding agents.

- **URL**: https://apify.com/m\_ctim/package-health-checker.md
- **Developed by:** [Timothy Kelvin](https://apify.com/m_ctim) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

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

## Dependency Health Checker: npm & PyPI Vulnerabilities

Paste a `package.json` or `requirements.txt`, or list packages, and get **one clean row per dependency**: the version you'd install, the latest version and how far behind you are, whether it's **deprecated or yanked**, its **known vulnerabilities** with the version that fixes each one, license, maintainers, weekly downloads, optional GitHub signals, and a **0-100 health score** with plain-language flags.

It's built to be called by **AI coding agents** and CI jobs as well as people: simple input, predictable output, every field always present.

### Who it's for

- **Developers and reviewers** checking a project's dependencies before a release or an upgrade.
- **AI coding agents** deciding whether a package is safe to add ("is `request` still maintained?").
- **Security and platform teams** doing a quick dependency audit across repos without installing anything.

### Input examples

Check a whole manifest (the default is this example, with healthy, outdated, vulnerable and deprecated packages):

```json
{
  "packageJson": "{\"dependencies\": {\"react\": \"^18.2.0\", \"express\": \"^4.17.1\", \"lodash\": \"4.17.15\", \"request\": \"^2.88.2\", \"left-pad\": \"^1.3.0\", \"chalk\": \"^4.1.2\", \"axios\": \"^1.7.0\"}, \"devDependencies\": {\"jest\": \"^29.7.0\"}}"
}
```

A Python project, with GitHub signals:

```json
{
  "requirementsTxt": "requests==2.19.0\ndjango>=4.2,<5\nnumpy~=1.26.0\npyyaml==5.3\n",
  "includeGithub": true
}
```

Individual packages, e.g. from an agent:

```json
{
  "packages": ["npm:react@18.2.0", "npm:@types/node", "pypi:requests==2.19.0", "pypi:django"]
}
```

### Input fields

| Field | What it does |
|---|---|
| `packages` | `npm:<name>[@version or range]` or `pypi:<name>[specifier]`. No version means latest. |
| `packageJson` | A package.json as text. `dependencies` and `devDependencies` are checked. `file:`, `git`, `workspace:` and GitHub shorthand dependencies are skipped with a warning. |
| `requirementsTxt` | A requirements.txt as text. Extras, environment markers, comments and `--hash` are handled; `-r`, `-e` and URL requirements are skipped. |
| `includeGithub` | Also read stars, archived status and last push from the linked GitHub repo. Off by default. |
| `maxItems` | Check at most this many packages (default 500). |

**Which version is checked?** Ranges are resolved to what a fresh install would get: npm `^4.17.1` becomes the highest matching 4.x release, PyPI `>=4.2,<5` the highest matching stable release (yanked releases are skipped unless pinned with `==`). The row shows both `requestedVersion` and `resolvedVersion`.

### Output

A real row from the default run (two of the four advisories shown):

```json
{
  "ecosystem": "npm",
  "name": "lodash",
  "requestedVersion": "4.17.15",
  "isDevDependency": false,
  "status": "ok",
  "resolvedVersion": "4.17.15",
  "latestVersion": "4.18.1",
  "isOutdated": true,
  "versionsBehind": { "major": 0, "minor": 1, "patch": null, "releases": 9 },
  "lastPublishedAt": "2026-04-01T21:01:20.458Z",
  "daysSinceLastRelease": 177,
  "weeklyDownloads": 164038197,
  "maintainersCount": 1,
  "license": "MIT",
  "deprecated": false,
  "deprecationMessage": null,
  "repositoryUrl": "git+https://github.com/lodash/lodash.git",
  "githubStatus": "not_requested",
  "githubStars": null,
  "githubArchived": null,
  "githubLastPush": null,
  "vulnerabilities": [
    {
      "id": "GHSA-35jh-r3h4-6jhm",
      "aliases": ["CVE-2021-23337", "CVE-2026-4800", "GHSA-r5fr-rjxr-66jc"],
      "summary": "Command Injection in lodash",
      "severity": "HIGH",
      "fixedIn": ["4.17.21"],
      "url": "https://osv.dev/vulnerability/GHSA-35jh-r3h4-6jhm"
    },
    {
      "id": "GHSA-p6mc-m468-83gw",
      "aliases": ["CVE-2020-8203"],
      "summary": "Prototype Pollution in lodash",
      "severity": "HIGH",
      "fixedIn": ["4.17.19"],
      "url": "https://osv.dev/vulnerability/GHSA-p6mc-m468-83gw"
    }
  ],
  "vulnCount": 4,
  "healthScore": 35,
  "healthFlags": ["known_vulns", "single_maintainer"],
  "error": null,
  "sourceUrl": "https://www.npmjs.com/package/lodash",
  "scrapedAt": "2026-09-26T00:47:02.481Z"
}
```

The rest of that run: `react@18.3.1` 90 (a major behind), `express@4.22.3` 90, `request@2.88.2` 30 (deprecated, a known vulnerability, no release since 2020), `left-pad@1.3.0` 40 (deprecated), `chalk@4.1.2` 85, `axios@1.20.0` 95, `jest@29.7.0` 90.

#### Field notes

- **`status`**: `ok`, `not_found` (no such package), `version_not_found` (nothing published matches the spec), or `registry_error`. Every row has every field; the ones that can't be known are `null`.
- **`versionsBehind`**: `major` is the major-version gap; `minor` and `patch` are filled only when the parts above them match; `releases` counts stable releases after yours.
- **`lastPublishedAt` / `daysSinceLastRelease`**: the newest release of any version line, so a backport to an older line counts as activity.
- **`deprecated`**: npm's deprecation message on the package or your version; on PyPI, the "Development Status :: 7 - Inactive" classifier or a yanked release.
- **`vulnerabilities`**: from [OSV](https://osv.dev), which aggregates the GitHub Advisory Database, PyPA and others. The same issue listed under two IDs (they reference each other, or share a CVE) is counted once. `severity` is GitHub's rating where there is one, otherwise computed from the advisory's CVSS 3.x vector, otherwise `UNKNOWN`. `fixedIn` is the first fixed version above yours; empty means no fix has been published.
- **`weeklyDownloads`**: npm only (PyPI doesn't publish download counts in its API).
- **`maintainersCount`**: npm only; PyPI has no public maintainer list.
- **`githubStatus`**: `ok`, `not_requested`, `no_repository`, `no_github_repo` (repo hosted elsewhere), `repo_not_found`, `rate_limited`, or `error`.

### How the health score works

Every package starts at **100**. Points are subtracted only for problems actually found; missing data never costs points.

| Problem | Points | Flag |
|---|---|---|
| Known vulnerabilities in the checked version: critical 30, high 20, medium 10, low 5, unknown 10 each, **capped at 60** in total | up to 60 | `known_vulns` |
| Deprecated package or version (npm deprecation, PyPI "Inactive" classifier or yanked release) | 40 | `deprecated` |
| GitHub repository archived (only with `includeGithub`) | 30 | `archived_repo` |
| No release in over 2 years | 20 | `no_release_2y` |
| No release in over 1 year (instead of the above) | 10 | `no_release_1y` |
| Checked version is a major version behind | 10 | `major_behind` |
| Only one npm maintainer | 5 | `single_maintainer` |

The score is clamped to 0-100. Worked example, `lodash@4.17.15` above: two high (20 + 20) and two medium (10 + 10) advisories make 60, at the cap; one maintainer takes 5 more: 100 - 60 - 5 = **35**. The formula lives in one pure function (`src/score.js`) with its own tests, so it's easy to audit.

### GitHub rate limit

GitHub allows 60 unauthenticated requests per hour per IP. Each distinct repository is fetched once per run. When the limit runs out, the remaining rows get `githubStatus: "rate_limited"` and the run carries on; nothing fails.

### FAQ

**Does it install anything or run code from packages?** No. It only reads public registry metadata (registry.npmjs.org, api.npmjs.org, pypi.org) and OSV's API. No proxies, no logins.

**Transitive dependencies?** Not in this version: it checks the packages you list or the direct dependencies of the manifest you paste.

**How is this different from `npm audit` or `pip-audit`?** Those need the project installed and cover vulnerabilities only. This works from a manifest or a list, covers both ecosystems in one format, and adds maintenance signals (deprecation, staleness, maintainers, archived repos) and a single score.

**Why is a very popular package flagged `single_maintainer`?** The npm registry lists one maintainer account for it. Some large projects publish from a single bot or owner account, so treat this flag as a small nudge, which is why it's only 5 points.

### Pricing

Pay per package checked. Packages that aren't found, or have no version matching your spec, are free.

### Disclaimer

This actor is unofficial and is not affiliated with, endorsed by, or connected to npm, Inc., GitHub, the Python Software Foundation, PyPI or Google's OSV project. Vulnerability data comes from OSV and its upstream databases and can lag new disclosures. A high score is a signal, not a security guarantee.

# Actor input Schema

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

One per line: "npm:react@18.2.0", "npm:@types/node", "pypi:requests==2.19.0", "pypi:django". Leave the version out to check the latest. npm ranges like "^4.17.0" and PyPI specifiers like ">=2,<3" are resolved to the version a fresh install would get.

## `packageJson` (type: `string`):

Paste a package.json. Its dependencies and devDependencies are checked; git, file and workspace dependencies are skipped.

## `requirementsTxt` (type: `string`):

Paste a requirements.txt. Lines like "requests==2.19.0", "django>=4.2,<5", "flask". Options (-r, -e) and URL requirements are skipped.

## `includeGithub` (type: `boolean`):

Add stars, archived status and last push from the linked GitHub repository. Uses GitHub's unauthenticated API (60 requests per hour); past the limit, rows get githubStatus "rate\_limited" instead of failing.

## `maxItems` (type: `integer`):

Check at most this many packages.

## Actor input object example

```json
{
  "packageJson": "{\n  \"name\": \"example-app\",\n  \"dependencies\": {\n    \"react\": \"^18.2.0\",\n    \"express\": \"^4.17.1\",\n    \"lodash\": \"4.17.15\",\n    \"request\": \"^2.88.2\",\n    \"left-pad\": \"^1.3.0\",\n    \"chalk\": \"^4.1.2\",\n    \"axios\": \"^1.7.0\"\n  },\n  \"devDependencies\": {\n    \"jest\": \"^29.7.0\"\n  }\n}",
  "includeGithub": false,
  "maxItems": 500
}
```

# Actor output Schema

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

No description

# 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 = {
    "packageJson": `{
  "name": "example-app",
  "dependencies": {
    "react": "^18.2.0",
    "express": "^4.17.1",
    "lodash": "4.17.15",
    "request": "^2.88.2",
    "left-pad": "^1.3.0",
    "chalk": "^4.1.2",
    "axios": "^1.7.0"
  },
  "devDependencies": {
    "jest": "^29.7.0"
  }
}`
};

// Run the Actor and wait for it to finish
const run = await client.actor("m_ctim/package-health-checker").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 = { "packageJson": """{
  \"name\": \"example-app\",
  \"dependencies\": {
    \"react\": \"^18.2.0\",
    \"express\": \"^4.17.1\",
    \"lodash\": \"4.17.15\",
    \"request\": \"^2.88.2\",
    \"left-pad\": \"^1.3.0\",
    \"chalk\": \"^4.1.2\",
    \"axios\": \"^1.7.0\"
  },
  \"devDependencies\": {
    \"jest\": \"^29.7.0\"
  }
}""" }

# Run the Actor and wait for it to finish
run = client.actor("m_ctim/package-health-checker").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 '{
  "packageJson": "{\\n  \\"name\\": \\"example-app\\",\\n  \\"dependencies\\": {\\n    \\"react\\": \\"^18.2.0\\",\\n    \\"express\\": \\"^4.17.1\\",\\n    \\"lodash\\": \\"4.17.15\\",\\n    \\"request\\": \\"^2.88.2\\",\\n    \\"left-pad\\": \\"^1.3.0\\",\\n    \\"chalk\\": \\"^4.1.2\\",\\n    \\"axios\\": \\"^1.7.0\\"\\n  },\\n  \\"devDependencies\\": {\\n    \\"jest\\": \\"^29.7.0\\"\\n  }\\n}"
}' |
apify call m_ctim/package-health-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,m_ctim/package-health-checker"
        }
    }
}
```

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/FmSq3D7JnmlrTVSGX/builds/LCAFIDfg8DSk2wUdn/openapi.json
