# Malicious dependencies

> How inspect.software identifies dependencies reported as malicious packages by the OpenSSF corpus, why they are scored apart from vulnerabilities, and what the finding does and does not claim.


A vulnerable dependency is a mistake. A **malicious** dependency is an attack.
They are not the same finding, and this record does not score them the same
way.

**Malicious dependencies** asks one question: does the dependency graph this
repository resolves to contain a package that has been reported as malware?

## Where the evidence comes from

The source is the OpenSSF
[malicious-packages](https://github.com/ossf/malicious-packages) corpus — an
openly licensed, community-maintained record of packages found to be malicious
across public registries: typosquats, account takeovers, dependency-confusion
attacks, and packages shipping prebuilt malicious binaries.

[OSV.dev](https://osv.dev) ingests that corpus and serves it under `MAL-`
identifiers alongside ordinary security advisories. Because this record already
queries OSV for [dependency advisories](/wiki/dependency-advisories), malicious
packages arrive on a query that was already being made. The finding costs no
additional request, no additional wait, and no additional dependency on a third
party.

## Why it is scored apart from advisories

Until methodology v1.10.0 these reports were counted as ordinary advisories,
and that was wrong in a way worth stating plainly.

A malicious-package report carries no CVSS vector and no severity label — there
is nothing to score, because malware has no severity, only a state. Scored as
an advisory it fell through to `unknown` severity, which is weighted like a
*moderate* vulnerability. A package that had been found to steal credentials
counted for slightly less than an average CVE.

The three properties that make a vulnerability scoreable are all absent:

- **No fixed version.** There is no corrected release of the same artifact to
  upgrade to. The remedy is removal, or moving off the compromised name.
- **No partial exposure.** A vulnerability may sit in an unreachable code path.
  An install-time payload runs when the package is installed.
- **No degrees.** A package either is or is not reported as malicious.

So a malicious dependency is now removed from the advisory findings entirely
and treated as a **red flag**: a multiplier and a ceiling rather than points
off. It applies a 35% multiplier to the [security
posture](/wiki/security-posture) score and to the weighted health index, and
caps both at **29 — the top of the Critical band**. That is one band stricter
than the [high-risk jurisdiction](/wiki/jurisdiction-exposure) ceiling of 49,
because this is a confirmed compromise of the software rather than an exposure
to a risk.

A package carrying both a malicious report and ordinary CVEs appears only under
this finding. Its vulnerabilities are moot.

## Withdrawn artifacts are reported, not scored

A registry that removes a malicious package settles the question: nothing
installable remains, and a repository that resolves to it is not shipping
malware today. Every finding therefore checks whether the registry still serves
that **exact resolved version**. Where it does not, the finding stays on the
report — the project depends on a name that was compromised, which is worth
seeing — but it raises no flag and costs no points.

The check deliberately asks about the resolved version rather than the
package's latest one. After a takedown npm leaves a placeholder release, marked
`-security`, as the package's latest. That protects everyone who resolves a
version range, and nobody who pinned the bad version exactly. Reading the
latest version would have cleared the first repository this finding ever fired
on, which pins the compromised package at the precise version the registry
still serves.

Where the question cannot be answered — an ecosystem the check does not cover,
or a registry that did not respond — the finding is scored as though the
package were live. Failing to reach a registry is not evidence that malware was
withdrawn.

## Direct and indirect count alike

Most of this record's dependency scoring distinguishes a declared direct
dependency from one pulled in transitively, because a maintainer chose the
first and inherited the second.

That distinction does not apply here. A package's install script runs at
whatever depth it sits in the resolved graph; a credential stealer four levels
down compromises the machine exactly as thoroughly as one named in the
manifest. Both are reported, both are scored, and the report states which is
which so a reader can see where the package entered.

## What the finding does not claim

**It is not an accusation against the maintainers.** The report concerns the
package *as published* by whoever published it. A repository resolves to
thousands of packages it never inspected; inheriting a compromised one is the
normal way this happens, and it says nothing about the intent or competence of
the people maintaining the scanned repository.

**It is not a claim the code was executed.** The finding says the resolved
dependency graph contains the package. Whether anyone installed that exact
graph, and what the payload did if they did, is outside what a public record
can observe.

**It is not our determination.** The classification is the OpenSSF corpus's,
reached through its own process and its own sources, and this record reports it
rather than reproducing it. A report withdrawn upstream disappears from the
next scan.

## How often it fires

Rarely — by design and by measurement. Before this metric shipped it was run
over a 300-repository sample covering 46,889 resolved dependencies, and it
found nothing.

That is the expected result, and it is not a sign the check is useless.
Registries pull malicious packages within hours to days of discovery, so a
lockfile that still resolves to one is unusual. The value of this finding is
not that it fires often; it is that when it does fire, it is the single most
important fact in the report, and it will no longer be averaged into a
vulnerability score.

## When it is not assessed

Like [dependency advisories](/wiki/dependency-advisories), this metric is
excluded — not scored zero — whenever the dependency graph or the OSV lookup
was unavailable. A repository is never penalized for having GitHub's dependency
graph switched off; the report says the check did not run instead of implying a
clean result.
