GitHub Security Advisory Export avatar

GitHub Security Advisory Export

Pricing

$15.00 / 1,000 advisory new or updateds

Go to Apify Store
GitHub Security Advisory Export

GitHub Security Advisory Export

Durable monitor for GitHub Security Advisories (GHSA/CVE) on your package watch-list — npm, PyPI, Maven, RubyGems, Go, NuGet, Composer and more. Diffs each check against what you've already seen and charges only for genuinely new or updated advisories; a clean check costs nothing extra.

Pricing

$15.00 / 1,000 advisory new or updateds

Rating

0.0

(0)

Developer

Radu Furtuna

Radu Furtuna

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Durable monitor for GitHub Security Advisories (GHSA) and their linked CVEs, across your dependency watch-list — npm, PyPI (pip), Maven, RubyGems, Go, NuGet, Composer, Rust, and more — via the official GitHub REST API (GET /advisories).

Why

Dependency scanners give you a snapshot; this watches your list continuously and tells you only what changed since your last check — a genuinely new advisory against a package you track, or a meaningful update to one you've already seen (a patched version just landed, severity was revised). A clean check costs nothing beyond the fixed platform run cost.

How it works

  1. Each watch is either:
    • a package watch{watchId, ecosystem, package} — every advisory affecting that package in that ecosystem; or
    • a GHSA watch{watchId, ghsaId} — updates to one specific advisory you already know about (useful once you're tracking a disclosed CVE and waiting for a fix).
  2. Every run fetches the current advisories for each watch and diffs them against a durable checkpoint (per watch: which (ghsaId, updatedAt) pairs were already seen).
  3. Genuinely new advisories, or advisories whose updatedAt moved forward since the last check, are pushed to the dataset and billed once each (advisory-new-or-updated). The first check of a new watch establishes a baseline (no charge) — you're not billed for the entire existing history the first time you point a watch at a popular package.

Input

{
"monitorId": "my-dependencies",
"watches": [
{ "watchId": "lodash-npm", "ecosystem": "npm", "package": "lodash" },
{ "watchId": "django-pip", "ecosystem": "pip", "package": "django" },
{ "watchId": "log4j-cve", "ghsaId": "GHSA-jfh8-c2jp-5v3q" }
],
"notifyOn": "new_alerts",
"webhookUrl": "https://example.com/webhook"
}

Add more watches later under the same monitorId — each watch keeps its own independent history.

Billing

Pay-per-event: advisory-new-or-updated — charged only for a (ghsaId, updatedAt) pair genuinely new since the previous check of that watch. If the same advisory matches two of your watches in one run (e.g. you track both lodash and lodash-es), it is billed once, not twice. Failed/blocked checks are never charged.

Delivery guarantee: at-most-once (not exactly-once)

The right to write a row and to charge for it is granted by a single atomic primitive — one addRequest(uniqueKey) into a dedicated, named claim-journal Request Queue (<prefix>-<monitorId>-claims). Exactly one run ever wins that key. Claim requests are never deleted and never handled: the queue is a permanent journal of irreversible attempts, not a work list.

What this buys you, stated honestly:

  • You will never be charged twice for the same event. That is the guarantee.
  • It is not exactly-once. If a run wins the claim and then dies before the row reaches the dataset (or before the charge completes), that event is lost: it closes as dataset_unknown / charge_unknown and is never re-delivered. We deliberately prefer losing a delivery over double-charging you.
  • Boundary of the guarantee: it holds for as long as the named claim-journal queue exists. Anyone with account access can delete or re-create that queue through the Apify Console/API; a fresh journal starts empty, and previously delivered events could then be delivered and billed again. That is an inherent limit of any durable storage, not a defect of the protocol.
  • Migration boundary: the guarantee applies from the build that introduced the claim gate onward. Older builds of this actor must not keep running against the same monitorId — they predate the journal and would not see the claims it holds.
  • coverage.claimJournalSize reports the journal's size each run (best-effort; null if the queue's metadata could not be read, and the value lags by a few seconds because Apify's totalRequestCount is eventually consistent). Use it to watch growth, not to make decisions.

One-time storage rename in the claim-gate build

Named storages used to be derived from the full actor name (31 characters). With a 40-character monitorId that produced a 72-character storage name — over Apify's 63-character limit, so long monitorIds could not work at all. They are now derived from a short prefix (ghsa-export), which keeps every name — including the new -claims queue — inside the limit. The one-time cost: durable checkpoints start empty, so the first run of each watch after this build is a baseline. A baseline delivers no rows and charges nothing, so this cannot cause double billing; you only lose one delta window.

Honest limits — read this before scheduling

No GitHub token is used. GET /advisories works fully unauthenticated for public advisory data, but GitHub caps unauthenticated REST calls at 60 requests/hour per IP — confirmed live (12.09.2026): a plain unauthenticated request returned HTTP 200 with x-ratelimit-limit: 60. We deliberately did not create a new GitHub account/token to raise this — that decision belongs to whoever runs this Actor. If you need a higher ceiling (5,000 requests/hour), fork this Actor and add Authorization: Bearer <your PAT> to src/ghsa_client.py; nothing else needs to change.

To make the 60/hour ceiling non-binding in practice:

  • Package watches sharing an ecosystem share one HTTP request. Ten npm watches cost the same one request as one npm watch (affects=pkg1,pkg2,...). GitHub does not document a fixed package-count limit for affects — the real constraint is request URL length, which this Actor's own 20-watches-per-run cap keeps comfortably safe in practice. Only distinct ecosystems (and GHSA watches, which are one request each since a specific advisory can't be batched) add requests.
  • Up to 20 watches per run; worst case (every watch a different ecosystem, or all GHSA watches) is 20 requests per run, doubled to 40 with retries — comfortably under 60/hour for a run scheduled at most once an hour.
  • If GitHub's rate limit is close to exhausted mid-run (checked from the previous response's own x-ratelimit-remaining header, not guessed), remaining watches for that run are skipped honestly with reason rate_limited_by_github — not silently reported as "nothing new" — and coverage in the run's key-value store reports the exact githubRateLimitRemaining/githubRateLimitLimit GitHub returned.
  • Run this on a schedule of once an hour or slower. More frequent schedules risk starving the shared 60/hour budget, especially if you also query api.github.com from the same IP for anything else.

Other honest limits:

  • Each check fetches up to 100 advisories per ecosystem group (GitHub's own per_page maximum, sorted newest-updated-first) — sufficient for monitoring, since changes appear at the top. coverage.windowFullCount flags when a group has more than fit on that page since the last check (informational, not an error).
  • The per-watch seen-set is capped at 500 (ghsaId, updatedAt) pairs (FIFO by discovery order); only matters for a package with an extraordinary volume of advisory churn.
  • We don't invent data: if the API response shape changes, or a watch's ecosystem/ghsaId is invalid, the run reports it honestly instead of silently returning zero results.

Author: OmniCoder (https://t.me/OmniCoder)