# GitHub Advisories Scraper: GHSA Security Database (`arman-bd/github-advisories-scraper`) Actor

Scrape the GitHub Advisory Database: GHSA identifiers, CVE aliases, severity, affected package ranges, patched versions and CWEs. Curated and often ahead of NVD.

- **URL**: https://apify.com/arman-bd/github-advisories-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Developer tools, SEO tools, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.74 / 1,000 advisory scrapeds

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

## GitHub Advisories Scraper: GHSA Security Database

![GitHub Advisories Scraper: GHSA identifiers, CVE aliases, severity, CVSS and every affected package range, straight from GitHub's advisory database](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/github-advisories-scraper.jpg)

**GitHub Advisories Scraper** exports the **GitHub Advisory Database** as structured records, GHSA identifier, CVE alias, severity, CVSS score and vector, CWE list, every affected package with its vulnerable version range and first patched version.

GitHub reviews and publishes advisories itself, often days before the same finding lands in NVD, and it maps each one to concrete package ranges rather than free-text product names. This Actor reads GitHub's published advisory data directly: **no browser, no proxies, no login required.** A token is optional and only raises the request budget.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/github-advisories-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/github-advisories-scraper.md
```

### What you get

| Output field | Meaning |
|---|---|
| `ghsaId`, `cveId` | GitHub's advisory ID and the CVE it maps to (`null` when no CVE has been assigned yet) |
| `summary`, `description` | One-line title and the full Markdown write-up: impact, patches, workarounds |
| `severity` | `critical`, `high`, `medium`, `low` or `unknown` |
| `cvssScore`, `cvssVector`, `cvssVersion` | Base score, the full vector string, and which CVSS revision it came from |
| `cweIds` | Every CWE the advisory is classified under, e.g. `["CWE-94", "CWE-306"]` |
| `ecosystem`, `packageName` | The primary affected package, registry and name |
| `vulnerableVersionRange`, `firstPatchedVersion` | The exact affected range and the version that fixes it |
| `affected` | **Every** affected package, not just the primary one, see the note below |
| `publishedAt`, `updatedAt` | Advisory timestamps |
| `references` | External links: patches, issues, vendor bulletins |
| `url` | The human-readable advisory page on github.com |
| `scrapedAt` | Run timestamp |

A `RUN_SUMMARY` record in the key-value store holds per-run counts, remaining rate-limit budget, the filters used, and any sweep that failed.

**On `affected`.** One advisory routinely covers several packages, `GHSA-r5fr-rjxr-66jc` alone lists `lodash`, `lodash-es`, `lodash-amd` and `lodash.template`. The dataset row stays flat and one-per-advisory, so the scalar `ecosystem` / `packageName` / `vulnerableVersionRange` / `firstPatchedVersion` fields describe the *primary* entry, and `affected` carries the complete list. When you set `affectsPackage`, the primary is the entry for that package, so the scalar fields say what you asked about.

### Common use cases

**Track advisories for your dependency set.** Sweep only the ecosystems you actually ship, on a schedule, and diff on `ghsaId`.

```json
{
 "ecosystems": ["npm", "pip", "go"],
 "publishedFrom": "2026-07-01",
 "maxResults": 1000
}
```

**Cross-reference GHSA against NVD.** Pull everything with a CVE alias and join on `cveId` to spot findings GitHub has published and NVD has not yet enriched.

```json
{
 "severities": ["critical", "high"],
 "publishedFrom": "2026-01-01",
 "maxResults": 2000
}
```

**Feed a security alerting workflow.** Watch one package and alert whenever a new `ghsaId` appears for it.

```json
{
 "affectsPackage": "lodash",
 "ecosystems": ["npm"]
}
```

### Quick start

Everything new and serious, across every ecosystem:

```json
{
 "severities": ["critical", "high"],
 "publishedFrom": "2026-01-01"
}
```

Everything, with a token so the 60/hour ceiling stops mattering:

```json
{
 "ecosystems": ["npm", "pip", "maven", "go"],
 "publishedFrom": "2025-01-01",
 "githubToken": "ghp_your_token_here",
 "maxResults": 20000
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `ecosystems` | array | `[]` | `npm`, `pip`, `rubygems`, `maven`, `nuget`, `composer`, `go`, `rust`, `erlang`, `actions`, `pub`, `swift`, `other`. Empty = all ecosystems in one sweep. |
| `severities` | array | `[]` | `critical`, `high`, `medium`, `low`, `unknown`. `moderate` is accepted and normalised to `medium`. Empty = all. |
| `affectsPackage` | string | `""` | Exact registry package name, e.g. `lodash`, `django`, `org.apache.commons:commons-text`. |
| `publishedFrom` | string | `""` | `YYYY-MM-DD`. Advisories published on or after this date. |
| `githubToken` | string | `""` | Optional PAT. Raises 60 → 5000 requests/hour. Stored as a secret. |
| `maxResults` | integer | `500` | Cap across all sweeps. `0` = no cap. |

**Which combinations make sense.** `ecosystems` and `severities` are the two filters GitHub applies server-side one value at a time, so the Actor fans them out: three ecosystems × two severities is **six** separate paginated sweeps, each burning its own rate-limit budget. Unauthenticated. That is realistically the whole hourly allowance. If you want breadth, prefer a single broad sweep with `publishedFrom` and filter afterwards; if you want depth on one ecosystem, narrow with `severities`. `affectsPackage` and `publishedFrom` are free. They are applied inside the same request.

### Output example

```json
{
 "ghsaId": "GHSA-r5fr-rjxr-66jc",
 "cveId": "CVE-2026-4800",
 "summary": "Prototype pollution in lodash template compilation",
 "description": "### Impact\n\nThe template compiler …",
 "severity": "high",
 "cvssScore": 8.1,
 "cvssVector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
 "cvssVersion": "3.1",
 "cweIds": ["CWE-1321"],
 "ecosystem": "npm",
 "packageName": "lodash",
 "vulnerableVersionRange": ">= 4.0.0, <= 4.17.23",
 "firstPatchedVersion": "4.18.0",
 "affected": [
 { "ecosystem": "npm", "packageName": "lodash", "vulnerableVersionRange": ">= 4.0.0, <= 4.17.23", "firstPatchedVersion": "4.18.0" },
 { "ecosystem": "npm", "packageName": "lodash-es", "vulnerableVersionRange": ">= 4.0.0, <= 4.17.23", "firstPatchedVersion": "4.18.0" },
 { "ecosystem": "npm", "packageName": "lodash.template", "vulnerableVersionRange": ">= 4.0.0, < 4.18.0", "firstPatchedVersion": "4.18.0" }
 ],
 "publishedAt": "2026-04-01T23:51:12Z",
 "updatedAt": "2026-04-02T14:02:55Z",
 "references": [
 "https://github.com/lodash/lodash/security/advisories/GHSA-r5fr-rjxr-66jc",
 "https://nvd.nist.gov/vuln/detail/CVE-2026-4800"
 ],
 "url": "https://github.com/advisories/GHSA-r5fr-rjxr-66jc",
 "scrapedAt": "2026-08-06T12:00:00.000Z"
}
```

`RUN_SUMMARY` in the key-value store:

```json
{
 "sweepsRequested": 2,
 "sweepsFailed": 0,
 "failures": [],
 "advisoriesSaved": 500,
 "requestsUsed": 5,
 "rateLimited": false,
 "rateLimit": {
 "limit": 60,
 "remaining": 43,
 "resetsAt": "2026-08-06T13:12:11.000Z",
 "authenticated": false
 },
 "filters": {
 "ecosystems": ["npm"],
 "severities": ["critical", "high"],
 "affectsPackage": null,
 "publishedFrom": "2026-01-01",
 "maxResults": 500
 },
 "finishedAt": "2026-08-06T12:00:04.918Z"
}
```

### Limits and behaviour

- **60 requests per hour, per IP, unauthenticated.** This is the single constraint that matters. Advisories page at 100 per request, so an unauthenticated run can retrieve about **6,000 advisories per hour** at absolute best. A token lifts the budget to 5000 requests/hour, roughly 500,000 advisories, and it is the difference between this Actor being a toy and being usable at scale.
- **The budget is read from every response.** rate-limit headers and rate-limit headers are logged after each sweep and written into `RUN_SUMMARY.rateLimit`.
- **Exhaustion stops the run cleanly.** When the budget hits zero the Actor stops, logs the reset time, sets `RUN_SUMMARY.rateLimited: true`, and keeps everything it already saved. It does not hammer into a wall of 403s.
- **Multiple selections multiply cost.** The API takes one ecosystem and one severity per request, so N ecosystems × M severities is N×M paginated sweeps.
- **One failing sweep never aborts the run.** Errors land in `RUN_SUMMARY.failures` and the next sweep starts. The Actor only throws if *every* sweep failed.
- **Transient errors are retried.** 429 and 5xx get three attempts with linear backoff. A 422 (bad filter value) is fatal and reported immediately, because retrying it would only burn budget.
- **Duplicates are removed.** When sweeps overlap, an advisory is saved once, keyed on `ghsaId`.
- **Public data only.** No authentication is required, no personal data is collected, no access control is bypassed.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~github-advisories-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "ecosystems": ["npm"],
 "severities": ["critical", "high"],
 "publishedFrom": "2026-01-01",
 "maxResults": 200
 }'
```

### JavaScript example

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

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/github-advisories-scraper').call({
 ecosystems: ['npm', 'pip'],
 severities: ['critical'],
 publishedFrom: '2026-01-01',
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const a of items) {
 console.log(`${a.ghsaId} ${a.severity.padEnd(8)} ${a.packageName} → fixed in ${a.firstPatchedVersion}`);
}
```

### FAQ

**Do I need a proxy?** No. Proxy configuration is not required to run this Actor.

**Do I need a GitHub account?** Not to run it. You need one only if you want a token, which you almost certainly do for anything beyond a few hundred advisories.

**What scopes does the token need?** None. A classic PAT with zero scopes ticked, or a fine-grained token with read access to public repositories, is enough, the advisory database is public and the token is only there to identify you for rate-limiting.

**Why is `cveId` sometimes null?** GitHub publishes reviewed advisories as soon as they are triaged; the CVE is assigned separately and can lag by days. That gap is exactly why this data is worth pulling.

**Why did I get fewer advisories than `maxResults`?** Either the filter combination has fewer matches, or the rate limit was reached. `RUN_SUMMARY` distinguishes the two: `rateLimited: true` plus `resetsAt` tells you it was the budget.

**What happens if GitHub is unavailable?** 5xx responses are retried three times, then that sweep is recorded in `RUN_SUMMARY.failures` and the run continues with the next one.

**Can I schedule it?** Yes, it is designed for it. Set `publishedFrom` to the previous run's date and diff on `ghsaId`.

**Can I integrate it with something else?** Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.

# Actor input Schema

## `ecosystems` (type: `array`):

Package ecosystems to sweep. Each one is a separate paginated request against GitHub, so picking three ecosystems costs three times the rate-limit budget. Leave empty to cover all of them in a single sweep.

## `severities` (type: `array`):

Keep only advisories at these severity levels. GitHub's web UI calls the middle band "Moderate" but the API calls it "medium". both spellings are accepted here. Leave empty for every severity.

## `affectsPackage` (type: `string`):

Return only advisories that list this exact package. Use the registry name as published, not the repo name. 'lodash' for npm, 'django' for pip, 'org.apache.commons:commons-text' for Maven. Leave blank for every package.

## `publishedFrom` (type: `string`):

Lower bound on the advisory publication date, as YYYY-MM-DD. Use this on scheduled runs to pick up only what appeared since the last sweep.

## `githubToken` (type: `string`):

A GitHub personal access token raises the limit from 60 to 5000 requests/hour. Create one at github.com/settings/tokens. no scopes are needed, an empty classic token or a fine-grained token with 'Public repositories' read access is enough.

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

Stop after this many advisories across all sweeps. Each 100 advisories costs one GitHub request, so 500 uses five of the 60 unauthenticated requests per hour. Set 0 for no cap.

## Actor input object example

```json
{
  "ecosystems": [
    "npm",
    "pip"
  ],
  "severities": [
    "critical"
  ],
  "affectsPackage": "lodash",
  "publishedFrom": "2026-01-01",
  "maxResults": 500
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN\_SUMMARY record from the run's key-value store.

# 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 = {
    "ecosystems": [
        "npm"
    ],
    "severities": [
        "critical",
        "high"
    ],
    "publishedFrom": "2026-01-01"
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/github-advisories-scraper").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 = {
    "ecosystems": ["npm"],
    "severities": [
        "critical",
        "high",
    ],
    "publishedFrom": "2026-01-01",
}

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/github-advisories-scraper").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 '{
  "ecosystems": [
    "npm"
  ],
  "severities": [
    "critical",
    "high"
  ],
  "publishedFrom": "2026-01-01"
}' |
apify call arman-bd/github-advisories-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/github-advisories-scraper"
        }
    }
}

```

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/b3zNzEcgcuOWRCSf1/builds/ZGigxhsBN9Se4hoEc/openapi.json
