# Security Advisory

> A security advisory is a published record that specific package versions are vulnerable or malicious, naming affected versions, the fix, and severity.

Also known as: Security advisories, Security bulletin, Vulnerability advisory, Malware advisory, Advisory database

Source: https://socket.dev/glossary/security-advisory

Last updated: 2026-11-01

A security advisory, also called a security bulletin, is a published notice that specific versions of a software package are vulnerable or malicious. In open source it is a structured record naming the package, the affected version range, the fixed version, and a severity, which scanners like [npm audit](https://socket.dev/glossary/npm-audit) match against your lockfile. Advisories only cover threats someone already found.

The matching step is the whole mechanism. A scanner never reads the vulnerable code. It compares the package names and versions in your dependency tree against the version ranges in advisory records, and it raises an alert on every overlap. Everything a security advisory catches, and everything it misses, follows from that design.

The cost of that design is measured in hours. In [the axios compromise](https://socket.dev/blog/axios-npm-package-compromised), the malicious dependency [plain-crypto-js](https://socket.dev/blog/axios-npm-package-compromised) went live on npm at 23:59:12 UTC on March 30, 2026. Our scanner flagged it 6 minutes and 29 seconds later. The first advisory record with a public timestamp, OSV [MAL-2026-2306](https://osv.dev/vulnerability/GHSA-2x9r-6wxq-hrr7), was published 2 hours and 8 minutes after the upload. Until a record exists, npm audit has nothing to match malware against.

Our position: security advisories are the right mechanism for known vulnerabilities and the wrong primary defense against malware. An advisory is a record of what someone already noticed, written after the fact and matched by name and version. Use advisories as the floor of your vulnerability data. Do not let a clean advisory scan stand in for analyzing what a package actually does.

## What is a security advisory?

A security advisory is a publisher's formal statement about a flaw: which versions are affected, how serious it is, and which version fixes it. Maintainers, software vendors, Linux distributions, ecosystem security teams, and national CERTs all publish them. In open source, most advisories end up as JSON records in the [Open Source Vulnerability (OSV) format](https://socket.dev/glossary/osv), collected by aggregators such as the [GitHub Advisory Database](https://socket.dev/glossary/github-advisory-database) and OSV.dev.

A machine-readable advisory carries a small set of fields that do the work:

- **Identifier and aliases.** A database-native ID such as `GHSA-78xj-cgh5-2h22`, plus the same vulnerability's IDs elsewhere, usually a [CVE ID](https://socket.dev/glossary/common-vulnerabilities-and-exposures-cve).
- **Affected package and range.** An ecosystem, a package name, and range events: `introduced`, `fixed`, `last_affected`, or `limit`.
- **Fixed version.** The release to upgrade to, when one exists.
- **Severity.** A [CVSS](https://socket.dev/glossary/common-vulnerability-scoring-system) vector, a label such as `high`, or both.
- **Weakness and references.** A CWE ID, plus links to the report, the fix commit, and credits.
- **Timestamps.** `published`, `modified`, and `withdrawn`, each an RFC 3339 time in UTC.

The npm registry serves advisories to scanners through a bulk endpoint. Here is the live response for ip@2.0.0, fetched on November 1, 2026:

```bash
curl -s -X POST https://registry.npmjs.org/-/npm/v1/security/advisories/bulk \
  -H 'content-type: application/json' \
  -d '{"ip":["2.0.0"]}'
```

```json
{
  "ip": [
    {
      "id": 1101851,
      "url": "https://github.com/advisories/GHSA-2p57-rm9w-gvfp",
      "title": "ip SSRF improper categorization in isPublic",
      "severity": "high",
      "vulnerable_versions": "<=2.0.1",
      "cwe": ["CWE-918"],
      "cvss": {
        "score": 8.1,
        "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"
      }
    },
    {
      "id": 1114832,
      "url": "https://github.com/advisories/GHSA-78xj-cgh5-2h22",
      "title": "NPM IP package incorrectly identifies some private IP addresses as public",
      "severity": "low",
      "vulnerable_versions": "=2.0.0",
      "cwe": ["CWE-918"],
      "cvss": { "score": 0, "vectorString": null }
    }
  ]
}
```

Two details in that response show how much judgment hides inside a record. The first advisory affects `<=2.0.1`, and 2.0.1 is still the latest release of ip, published February 19, 2024, so there is no fixed version to upgrade to. The second advisory is rated `low` with a CVSS score of 0. It was filed as a 9.8 critical, and in [the node-ip severity dispute](https://socket.dev/blog/node-ip-maintainer-restores-github-repo-after-archiving-due-to-overblown-cve-rating) GitHub lowered it after the maintainer objected.

On GitHub, an advisory starts as a [repository security advisory](https://docs.github.com/en/code-security/concepts/vulnerability-reporting-and-management/about-repository-security-advisories). The maintainer opens a private draft, fixes the bug in a temporary private fork, and can request a CVE ID from GitHub, which is a CVE Numbering Authority (CNA). Publishing makes the advisory public, and GitHub's curators then map it to a package so that scanners can match it. A record with no package mapping is invisible to every scanner that matches by name.

## How do scanners match security advisories to your dependencies?

Scanners match security advisories by package name and version, never by inspecting code. npm audit sends the name and installed versions of every package in your tree to `/-/npm/v1/security/advisories/bulk`, and falls back to an older Quick Audit endpoint if that call fails, per [npm's documentation](https://docs.npmjs.com/cli/v12/commands/npm-audit/). Python tools do the equivalent against OSV data, which PyPI also exposes in the `vulnerabilities` field of its JSON API.

To see what that matching produces, we wrote a lockfile that pins axios@1.14.1, one of the two malicious axios releases, and ran npm 10.9.9 against it on November 1, 2026. The `--package-lock-only` flag audits the lockfile without installing anything, so nothing malicious runs.

```bash
$ npm audit --package-lock-only
# npm audit report

axios  1.0.0 - 1.19.0
Severity: critical
Malware in axios - https://github.com/advisories/GHSA-fw8c-xr5c-95f9
Axios has a NO_PROXY Hostname Normalization Bypass that Leads to SSRF - https://github.com/advisories/GHSA-3p68-rc4w-qgx5
Axios: Authentication Bypass via Prototype Pollution Gadget in `validateStatus` Merge Strategy - https://github.com/advisories/GHSA-w9j2-pvgh-6h63
# ... 32 more advisory lines ...
No fix available
node_modules/axios

1 critical severity vulnerability

$ npm audit --package-lock-only --json | jq -c '.metadata.vulnerabilities, (.vulnerabilities.axios.via | length)'
{"info":0,"low":0,"moderate":0,"high":0,"critical":1,"total":1}
35
```

npm received 35 advisories for that one version and reported one critical vulnerability. Thirty-four describe ordinary bugs with fixed versions. One says the package is malware. All 35 arrive in the same shape, and the summary collapses them into a single count.

Counting has a second trap: one vulnerability usually has several records. PyPI's JSON API returns six records for requests 2.31.0:

```bash
$ curl -s https://pypi.org/pypi/requests/2.31.0/json | jq -c '.vulnerabilities[] | {id, aliases, fixed_in}'
{"id":"GHSA-9wx4-h78v-vm56","aliases":["CVE-2024-35195","PYSEC-2026-1873"],"fixed_in":["2.32.0"]}
{"id":"GHSA-9hjg-9r4m-mvj7","aliases":["CVE-2024-47081","PYSEC-2026-1872"],"fixed_in":["2.32.4"]}
{"id":"GHSA-gc5v-m9x4-r6x2","aliases":["CVE-2026-25645","PYSEC-2026-2275"],"fixed_in":["2.33.0"]}
{"id":"PYSEC-2026-1873","aliases":["CVE-2024-35195","GHSA-9wx4-h78v-vm56"],"fixed_in":["2.32.0"]}
{"id":"PYSEC-2026-1872","aliases":["CVE-2024-47081","GHSA-9hjg-9r4m-mvj7"],"fixed_in":["2.32.4"]}
{"id":"PYSEC-2026-2275","aliases":["CVE-2026-25645","GHSA-gc5v-m9x4-r6x2"],"fixed_in":["2.33.0"]}
```

The requests output contains three vulnerabilities, not six. Each one appears as a GHSA record and as a PYSEC record from the Python Packaging Authority's advisory database, and both records carry the same CVE ID. The [OSV specification](https://ossf.github.io/osv-schema/) defines aliases as symmetric and transitive, so the correct unit to count is the alias group. A dashboard that counts IDs double-counts, and a ticket closed on one ID can leave its twin open.

## Security advisory vs CVE: what is the difference?

A CVE is an identifier, and a security advisory is the document that uses it. The CVE Program assigns `CVE-YYYY-NNNNN` IDs through CNAs. One CVE can appear in a dozen advisories from different vendors and distributions, and one advisory can bundle many CVEs. A growing share of open source advisories never get a CVE at all.

[The OpenClaw advisory surge](https://socket.dev/blog/openclaw-advisory-surge-highlights-gaps-between-ghsa-and-cve-tracking) made that gap visible. By late February 2026, within three weeks of going viral, the OpenClaw project had published more than 200 GitHub Security Advisories, and 170 of them did not yet have a CVE ID. Scanners, SBOM tools, and compliance workflows keyed only on CVE could not see those 170.

The CVE side of the pipeline is also thinning. In April 2026, [NIST stopped enriching most CVEs](https://socket.dev/blog/nist-officially-stops-enriching-most-cves) in the [National Vulnerability Database](https://socket.dev/glossary/national-vulnerability-database-nvd) after CVE submissions rose 263% between 2020 and 2025. Enrichment is the step that adds CVSS scores and the CPE product mappings that commercial scanners match on. As of August 17, 2026, the NVD listed 42,353 CVEs as "Not Scheduled," against 3,049 in its two active enrichment queues, per [our follow-up on NIST's AI plans](https://socket.dev/blog/nist-nvd-ai-automation).

Open source package advisories feel the NVD cuts less, because GHSA and OSV records carry the package name and version range themselves. Commercial products that depend on CPE matching feel them more. Either way, a pipeline that treats the CVE record as its only source of truth is now missing data by policy, not by accident.

## Advisory formats: OSV, CSAF, and the CVE record

Three formats carry most machine-readable advisories. OSV is the package-ecosystem format, and as of October 2026 its specification is at version 1.9.2. The Common Security Advisory Framework, [CSAF 2.0](https://socket.dev/glossary/common-security-advisory-framework-csaf-2-0), became an [OASIS Standard on November 18, 2022](https://www.oasis-open.org/?p=9299) and is what many vendors and Linux distributions publish. The CVE record is the CVE Program's own JSON for each ID.

| Property | OSV | CSAF 2.0 | CVE record |
| --- | --- | --- | --- |
| Identifies software by | Ecosystem and package name, or a package URL | A product tree of vendor products and versions | Vendor and product names; NVD adds CPE |
| Version semantics | SEMVER, ECOSYSTEM, or GIT ranges with introduced and fixed events | Per-product status such as known\_affected and fixed | Per-product version ranges |
| Exploitability (VEX) statements | No | Yes: known\_not\_affected status and a VEX profile | No |
| Used by | GHSA, PyPA, RustSec, Go, OSV.dev | Vendors and distributions | Every CNA |
| Best for | Dependencies resolved by a package manager | Vendor products and exploitability statements | Cross-referencing between databases |

Our verdict: use OSV for anything a package manager resolves, because it is the only one of the three that names the package the way your lockfile does. Use CSAF when you ship or consume vendor products and need to state that a component is present but not exploitable. Use the CVE ID as the join key between the two, not as the thing you match on.

## Are malware advisories security advisories?

Malware advisories are security advisories in every major database, and they break most of the format's assumptions. The GitHub Advisory Database [defines three record types](https://docs.github.com/en/code-security/concepts/vulnerability-reporting-and-management/github-advisory-database): reviewed, unreviewed, and malware, with malware records sourced from the npm security team and the OpenSSF Malicious Packages repository. OSV publishes the same reports under `MAL-` IDs. A [malicious package](https://socket.dev/glossary/malicious-package) fails the vulnerability schema in four places:

- **No fixed version.** GitHub's documentation says malware alerts do not contain a fix version. Every version the attacker published is the bad version.
- **Severity is invented.** The axios malware record carries the label `critical` with a CVSS score of 0 and no vector. The 2018 flatmap-stream malware record goes the other way, with a full CVSS 3.1 vector that scores it 9.8 as if it were a network-reachable bug.
- **Upgrading is not remediation.** The advisory text states it plainly: "Any computer that has this package installed or running should be considered fully compromised." The response is credential rotation and incident response, not a version bump.
- **The record always arrives after the attack.** Someone has to detect, confirm, and file before any scanner can match.

The axios timeline shows the last point to scale.

> Timeline of the axios compromise on March 30 and 31, 2026, showing malware published at T+0, Socket detection at 6 minutes, and the first OSV advisory records at 2 and 3 hours

| Event | UTC time | Time after T+0 |
| --- | --- | --- |
| plain-crypto-js@4.2.0 published, a clean copy of crypto-js used as a decoy | Mar 30, 05:57:32 | 18 hours before |
| plain-crypto-js@4.2.1 published with a postinstall dropper (T+0) | Mar 30, 23:59:12 | 0 |
| Socket flags plain-crypto-js@4.2.1 as malware | Mar 31, 00:05:41 | 6m29s |
| axios@1.14.1 published with plain-crypto-js@4.2.1 as a dependency | Mar 31, 00:21:58 | 22m46s |
| axios@0.30.4 published | Mar 31, 01:00:57 | 1h01m45s |
| OSV MAL-2026-2306 published for plain-crypto-js (alias GHSA-2x9r-6wxq-hrr7) | Mar 31, 02:07:58 | 2h08m46s |
| OSV MAL-2026-2307 published for axios 1.14.1 and 0.30.4 (alias GHSA-fw8c-xr5c-95f9) | Mar 31, 03:15:49 | 3h16m37s |
| npm replaces plain-crypto-js with a 0.0.1-security holding package | Mar 31, 03:25:11 | 3h25m59s |

Package publish times come from the npm registry's `time` field, the detection time from [our report on the compromise](https://socket.dev/blog/axios-npm-package-compromised), and the advisory times from the OSV records. GitHub's pages for the matching GHSA records show only the date, March 31. By the OSV timestamps, neither package had an advisory record for 2 hours and 8 minutes, and the poisoned axios@1.14.1 went 2 hours and 54 minutes without one. The dropper ran on `postinstall`, so wherever install scripts were enabled, the install itself executed it before any audit could report anything.

That gap is why Socket keeps the two signals apart. Socket's CVE alerts, from Critical CVE down to Low CVE, come from advisory data, including GitHub's feed. The [Known Malware alert](https://docs.socket.dev/docs/issues-list) does not wait for an advisory. It comes from our automated analysis of the published package contents, which is what flagged plain-crypto-js 16 minutes before the first poisoned axios release pulled it in.

Checking after the fact has its own blind spot. npm audit reads your lockfile, and [our analysis of the axios blast radius](https://socket.dev/blog/hidden-blast-radius-of-the-axios-compromise) showed that tools run through `npx` or installed globally resolve against the registry and never appear in a project lockfile. A clean audit the day after a compromise proves the project's lockfile is clean, not that no machine ran the malware.

Malware records also fail in the other direction. On May 26, 2026, [OSV withdrew 157 malware reports](https://socket.dev/blog/osv-withdraws-157-malware-reports) that automated detection had filed against legitimate npm and PyPI packages, including FastAPI 0.136.3. Tools enforcing those records blocked builds until the records moved to `withdrawn`. A consumer that ignores the `withdrawn` field keeps blocking them.

## Where security advisories go wrong

Security advisories are often written and scored by someone other than the maintainer of the code. The ip package shows the cost. CVE-2023-42282 was filed against it as a 9.8 critical, and the maintainer, Fedor Indutny, archived the repository in response, then restored it a week later when GitHub lowered the rating to low. "Someone filed a dubious CVE about my npm package, and then I started getting messages from all people getting warnings from npm audit," [Indutny wrote on Mastodon](https://socket.dev/blog/node-ip-maintainer-restores-github-repo-after-archiving-due-to-overblown-cve-rating).

Severity scores disagree even between careful publishers. In [the curl and Go CVSS rejection](https://socket.dev/blog/curl-project-and-go-security-teams-reject-cvss-as-broken), CISA scored curl's CVE-2024-11053 at 9.1 critical while the curl team rated it low. Across 1,567 CVEs that both NVD and GitHub scored, NVD came in higher 885 times and GitHub higher 682 times, per data we covered when NIST cut enrichment.

Status fields drift too. Until 2025, OSV converted CVEs marked "disputed" into withdrawn records, which hid them from every downstream consumer. [The OSV disputed-CVE fix](https://socket.dev/blog/google-osv-fix-adds-500-new-advisories), which our team pushed for, restored more than 500 advisories. One of them was CVE-2023-48022 in Ray, a vulnerability the vendor disputed and attackers exploited anyway.

Most records never get human review. A 2026 study of more than 288,000 GitHub Security Advisories found that only about 8% were GitHub-reviewed, and GitHub does not send alerts for unreviewed advisories. Unreviewed records usually lack the package mapping that matching needs.

The practical answer is to stop triaging by score. Fourteen of the 35 advisories on axios@1.14.1 are tagged CWE-1321, prototype pollution, and nearly all describe gadgets: code that turns an existing pollution of `Object.prototype` into impact. Those gadgets matter only if something else in the process can already pollute the prototype. [Reachability analysis](https://socket.dev/glossary/reachability-analysis), which checks whether your code can actually reach the vulnerable function, separates the few that matter from the rest far better than a CVSS number does.

## What we think should change

Publishers should ship a fixed version and package coordinates with every advisory, and maintainers need a real chance to dispute a record before it reaches millions of `npm audit` runs. Databases should stop forcing malware into vulnerability fields. A malware record with a fabricated CVSS vector, or a score of 0 labeled critical, teaches tools to treat an active compromise like a bug with a patch. Consumers should count alias groups, honor `withdrawn`, and treat advisories as the floor of their data, not the ceiling.

The harder problem is volume without verification. Advisory publishing is decentralizing, through GHSA-only disclosures and [GCVE's federated publishing ecosystem](https://socket.dev/blog/gcve-launches-decentralized-publishing-ecosystem), and automating, through machine-filed malware reports and NIST's planned AI enrichment tool. More records will arrive faster, with less human review behind each one. The bottleneck is no longer whether an advisory exists. It is whether anyone checked that it is true.
