A vulnerability is a mistake someone made. A malicious package is an attack someone planned. The first waits to be exploited; the second runs its payload the moment it is installed, often on a developer laptop or a build server holding exactly the credentials an attacker wants. Treating them as two rows in the same severity-sorted list misses the difference that matters.

In short

  • Known-malicious versions are always P1. InsightFortify checks every component against OSV’s malicious-package reports (the MAL- ids from the OpenSSF project) and raises a finding of its own kind.
  • It looks back as well as forward: when a version you already run is reported malicious later, the daily sync raises the finding on every repository and image whose latest SBOM contains it.
  • Weaker signals are flagged, not blocked by default: typosquats, npm install scripts, packages younger than 30 days, and a publisher change on a release from the last 180 days.
  • A package firewall stops it before install: one API call from CI or a registry proxy returns allow, warn or block under your organization’s policy.
  • A real example from our own scans: OWASP NodeGoat pins fsevents 1.2.9, which OSV lists as malicious (MAL-2023-462).

A real example, not a hypothetical

When we rescanned OWASP NodeGoat, the teaching application, InsightFortify raised one finding of kind malware: fsevents@1.2.9. That is not a false alarm. OSV lists fsevents versions 1.0.0 through 1.2.10 under MAL-2023-462. According to the advisory, those versions downloaded prebuilt binaries from a cloud storage location that later lapsed, so the binaries a build fetched could contain code nobody at the project wrote. A companion advisory (GHSA-8r6j-v8pm-fqw3) describes the same issue as code injection. The fix is to move to 1.2.11 or later.

Two points matter for a security leader. First, the package is not obscure: it is a common, transitive dependency of JavaScript tooling on macOS. Second, the advisory was published years after the affected versions, so a scan that only checks new installs would never have flagged a lockfile written before then.

Industry
data

Open-source registries remove thousands of malicious packages every year, and security researchers have reported sustained growth in malicious uploads to npm and PyPI. The OpenSSF malicious-packages project collects these reports in OSV format so that tools can check against them.

Sources: OpenSSF Package Analysis and malicious-packages repository; OSV.dev; annual software supply chain reports from security vendors (figures vary by method).

Why vulnerability scanners miss it

Dependency scanners are built around CVEs: a known flaw in a legitimate package, with a severity and a fixed version. Malicious packages rarely have a CVE. Many are removed from the registry within days, so a scanner that resolves versions live may not even see them. And the signals that precede a confirmed report — a name one character away from a popular package, a brand-new publisher, an install script — are not vulnerabilities at all.

VULNERABILITY-ONLY A CVE list, sorted by CVSS

Malicious versions without a CVE are invisible. Typosquats look like any other dependency. A version reported malicious next year is never re-checked.

SUPPLY-CHAIN AWARE Malicious first, signals second, policy at install

OSV malicious reports become P1 findings, checked again daily against what you already run. Suspicious signals are flagged for review, and the firewall decides at install time.

The signals, and how much weight each gets

SignalHow it is detectedDefault handling
Known maliciousVersion listed in OSV with a MAL- id; cached and re-checked dailyFinding of kind malware, +3.0, always P1; firewall blocks
TyposquatOne edit, a dropped separator or a look-alike character away from one of the 3,000 most downloaded packages in npm or PyPI, and not itself a widely used packageSupply-chain finding; firewall warns
Install scriptnpm lockfile says the package runs a script on install; popular packages and known native builds exceptedOff by default in the firewall; finding when seen
Young packageFirst published fewer than 30 days before it was checkedSupply-chain finding; firewall warns (threshold adjustable, 0 turns it off)
Publisher changenpm: a release from the last 180 days published by an account that never published the package beforeSupply-chain finding; firewall warns

Why the 180-day rule exists

Our first version flagged any publisher change, and it flagged a dozen long-lived NodeGoat dependencies whose maintainers handed over years ago. That is ordinary open-source life, not an attack. A takeover matters while the release is recent, so the signal now looks only at releases from the last 180 days. We caught this by running the product on real code before shipping.

The package firewall

Finding a malicious package after it is installed is useful; stopping the install is better. The firewall is a single API call that CI or a registry proxy makes with a list of package versions. It answers allow, warn or block for each, with the reasons, under the organization’s policy. It asks OSV live for versions it has not checked in the last day and falls back to its cache if OSV is unreachable, saying which it did.

sequenceDiagram participant Dev as Developer participant CI as CI pipeline or registry proxy participant FW as InsightFortify firewall participant OSV as OSV.dev Dev->>CI: adds a dependency, opens a PR CI->>FW: check npm:lodahs@1.0.0, npm:flatmap-stream@0.1.1 FW->>OSV: versions not checked today OSV-->>FW: MAL id for flatmap-stream 0.1.1 FW-->>CI: block flatmap-stream - malicious, warn lodahs - looks like lodash CI-->>Dev: build fails with the reasons Note over FW: falls back to the cache if OSV is unreachable, and says so

The firewall answers per package, with reasons, so the developer sees exactly what to change.

The default policy blocks known-malicious versions and versions with a critical, actively exploited vulnerability, and warns on suspected typosquats, recent publisher changes and packages first published in the last 30 days. Install-script warnings are available but off by default, because install scripts are common in legitimate packages. Every setting is per organization.

flowchart TD A["Package version"] --> B{"Known malicious?"} B -- "yes" --> X["Block"] B -- "no" --> C{"Critical and exploited
in the wild?"} C -- "yes" --> X C -- "no" --> D{"Typosquat, recent publisher
change, optional signals?"} D -- "yes" --> W["Warn"] D -- "no" --> OK["Allow"] style X fill:#fee2e2,stroke:#ef4444 style W fill:#fef3c7,stroke:#f59e0b style OK fill:#ccfbf1,stroke:#0d9488

The default decision order. Each step is a policy switch the organization controls.

Responding when one is found

Response to a malicious-package findingIllustrative runbook; adapt it to your incident process
1
Confirm the version and where it runsAppSec analyst

The finding lists every repository and image whose latest SBOM contains it.

2
Treat build hosts and laptops as possibly affectedIncident lead

A malicious package runs at install time: the risk is to wherever it was installed, not only production.

3
Rotate credentials reachable from those hostsPlatform team

Registry tokens, cloud keys and CI secrets first.

4
Pin or upgrade past the affected rangeDevelopers

For fsevents, 1.2.11 or later.

5
Turn on the firewall check in CIPlatform team

So the same version cannot come back through another repository.

The fsevents 1.2.9 malicious-package finding in InsightFortify, P1, with its score factors and the OSV report MAL-2023-462
The real finding from our NodeGoat scan. Known malicious, P1, with the OSV report and where the package travels.

The first moves of that runbook do not need a person. The malicious package playbook template starts on exactly this event, adds a note saying the version must be removed, labels the finding malicious, opens a Jira issue and alerts the security channel. Like every template it installs switched off. Below is a dry run of it against the real fsevents finding from NodeGoat: all six steps ran, conditions were evaluated for real, and nothing was sent.

A dry run of the malicious-package playbook against fsevents@1.2.9: trigger, note, label, Jira issue, Slack alert and end all complete, with the execution log below and nothing sent
The playbook, dry-run against the real finding. Six steps, all completed, nothing sent; switch it on when it reads right.

Typosquats, in practice

A typosquat is a package named to be installed by mistake. The patterns are well known, which is why they can be checked mechanically against a list of popular package names:

PatternIllustrative exampleWhat InsightFortify checks
One character changed or swappedlodahs for lodashEdit distance of one from a popular name
Separator dropped or addedreactdom for react-domNames equal once separators are removed
Look-alike charactersrequ1re for require-style namesHomoglyph substitutions such as 1/l and 0/o

A widely used package is never a suspect, however close its name is to another: about 17,000 npm names (the npm-high-impact list) and the top 15,000 PyPI projects by downloads are treated as legitimate, and a name is compared only against the 3,000 most downloaded in its ecosystem. Our first version used a list of a few hundred names, and it warned on safer-buffer and through2; the larger lists fixed that, while real past typosquats such as crossenv (npm, 2017) and colourama (PyPI, 2018) are still caught. Scoped npm names and Maven coordinates are not covered yet. Because similar names can be legitimate, the default is to warn, not block.

Writing the package policy

The firewall policy is short, but each switch is a business decision. Questions to settle with engineering leadership before turning on blocking:

  • Who can override a block, and how is it recorded? A malicious-package block should need a security lead; a typosquat warning can be acknowledged by the developer.
  • Where is the check enforced? In CI only, in a registry proxy for every install, or both. A proxy also protects developer laptops.
  • What about new packages? Warning on packages younger than 30 days slows adoption of brand-new libraries slightly; most teams accept that trade.
  • How are build hosts treated after a hit? Agree in advance that a confirmed malicious install is an incident, so nobody debates it at 2am.
MetricWhat good looks like
Known-malicious versions present in any SBOMZero, and each one closed within a day of discovery
Repositories behind the firewall checkAll production repositories, then developer tooling
Firewall warnings acknowledged versus ignoredWarnings are read; the rate of ignored warnings falls
Median age of the oldest unpinned dependencyFalling, as fewer surprises come from unreviewed upgrades

Why retroactive checking matters most

The fsevents case illustrates the least understood part of supply-chain risk: time. The affected versions were published long before the malicious-package report was. A lockfile written in between looks perfectly normal to any check that runs only when a dependency is added. The risk was introduced at install time; the knowledge arrived years later.

That is why InsightFortify keeps the malicious-package data in a shared cache, re-checks it daily, and compares it with the latest SBOM of every repository and image, not only with new scans. When a version you already run is reported, the finding appears on every repository and image that contains it, at P1, without anyone rescanning. For a security leader, that turns “were we affected by the incident in this morning’s news?” into a filter on the findings queue.

The same logic applies in the other direction. When a report is withdrawn or corrected upstream, the cached data changes on the next sync and the finding follows. The decision always rests on the current public record, not on whatever was true the day the dependency was added.

What it means for each reader

For the CISO

A malicious package is an incident, not a backlog item. The P1 rule and the retroactive check make sure it is treated as one.

The firewall is a control you can describe to auditors: what it blocks, what it warns on, and who sets the policy.

For the AppSec lead

Start the firewall in warn-only mode for a sprint, read the warnings, then turn on blocking for malicious versions.

If you turn on install-script warnings, expect some noise; that is why they are off by default.

For the board

Software supply chain attacks are a board-level topic. This is the answer to “would we know, and could we stop it?”

It uses public, independent data (OSV and OpenSSF), not a vendor’s private list.

Vulnerabilities wait to be exploited. Malicious packages run when they are installed.

Check what you already run against the malicious-package reports every day, and put a policy in front of the install.

Keep reading