# Vulnerability (OSV)<no value>
// <!-- Required for asciidoctor -->
:toc:
// Set toclevels to be at least your hugo [markup.tableOfContents.endLevel] config key
:toclevels: 4

[cols="1^,1^,1^",options=header]
|===
| source | condition | target
| &#10004; | &#10004; | &#10007;
|===

== Description

The `vulnerability/osv` resource checks a package version against the link:https://osv.dev[OSV vulnerability database],
which aggregates advisories such as GitHub Security Advisories, the Go vulnerability database and
the Python Packaging Advisory Database. Each vulnerability is reported under its lowest ID, with
its aliases (for example a `GHSA` advisory and its `CVE`) merged into one entry.

**source**::
Depending on `key`, returns either the lowest version, starting from `version`, that has no known
vulnerabilities (`fixedversion`, the default), or the comma-separated IDs of the known
vulnerabilities affecting `version` (`ids`).

**condition**::
Passes when the version has no known vulnerabilities, and fails with the list of vulnerabilities
otherwise. The version comes from `version`, or from the source output when `version` is empty.

**target**::
Not supported, a target fails with `target not supported for the vulnerability/osv plugin`. Use the
source output in a target of another kind, such as the link:/docs/plugins/resource/file/["File" resource]
or the link:/docs/plugins/resource/yaml/["Yaml" resource].

NOTE: An `scm` attached to a condition is ignored, with a warning. The lookup always goes to the OSV API.

== Parameters

{{< resourceparameters "sources" "vulnerability/osv" >}}

The generated table above stays empty until the Updatecli JSON schema published with this website
includes the `vulnerability/osv` resource. Until then, these are the parameters:

[cols="1,1,3",options=header]
|===
| Name | Stage | Description

| `ecosystem`
| source, condition
| The OSV ecosystem of the package, such as `PyPI`, `npm`, `Go`, `crates.io`, `Maven`, `Debian:12`
or `Ubuntu:22.04:LTS`. The value is case-sensitive: the OSV API rejects `pypi` or `debian`.

| `name`
| source, condition
| The package name within the ecosystem, such as `jinja2`, `golang.org/x/net`,
`org.apache.logging.log4j:log4j-core` or `@angular/core`.

| `purl`
| source, condition
| The package URL, without a version, such as `pkg:pypi/jinja2` or `pkg:npm/%40angular/core`.
Replaces `ecosystem` and `name`.

| `version`
| source, condition
| The package version to check. Required for a source. A condition falls back to the source output
when it is empty.

| `key`
| source
| What the source returns: `fixedversion` (default) or `ids`.

| `minseverity`
| source, condition
| Only account for vulnerabilities at or above this severity: `LOW`, `MODERATE` (or its alias
`MEDIUM`), `HIGH` or `CRITICAL`.

| `ignore`
| source, condition
| Vulnerability IDs or aliases to disregard, such as `GHSA-cpwx-vrp4-4pq7` or `CVE-2025-27516`.

| `url`
| source, condition
| The OSV API URL. Defaults to `https://api.osv.dev`.
|===

=== Identifying the package

Set either `ecosystem` and `name`, or `purl`. The resource validates the spec before running and
reports the problems it finds together, for example an unsupported `key` alongside a bad package:

* `purl` together with `ecosystem` or `name` fails with
`vulnerability/osv purl is mutually exclusive with ecosystem and name`.
* Neither fails with `vulnerability/osv package not defined, set ecosystem and name, or purl`.
* A `purl` carrying a version fails with
`vulnerability/osv purl "pkg:pypi/jinja2@3.1.2" must not contain a version, use the version field instead`.
* A source without `version` fails with `vulnerability/osv source requires a version`.

With `purl`, the ecosystem comes from the package URL type: `cargo` (crates.io), `composer`
(Packagist), `gem` (RubyGems), `golang` (Go), `hex` (Hex), `maven` (Maven), `npm` (npm), `nuget`
(NuGet), `pub` (Pub) and `pypi` (PyPI). Other package URL types are only matched on the package URL
itself, and `fixedversion` does not support them.

=== Severity and ignored vulnerabilities

`minseverity` relies on the severity of the GitHub advisory. A vulnerability without one is always
accounted for, whatever the threshold. Set it deliberately: with `minseverity: HIGH`, a version
whose advisories are all rated `MODERATE` passes the condition.

`ignore` matches both IDs and aliases, case-insensitively, so a vulnerability can be ignored by its
`CVE` number even when OSV reports it under a `GHSA` ID.

== The fixed version is a security floor

`fixedversion` answers "which version fixes the known vulnerabilities?", not "which version is the
latest?". It returns the lowest version, from `version` onwards, that clears the advisories affecting
`version`, and returns `version` itself when it has no known vulnerabilities.

* It supports the Go, npm, crates.io, NuGet, Hex, Pub and PyPI ecosystems. Every ecosystem works
with `key: ids` and with conditions.
* A pre-release fix is only considered when `version` is itself a pre-release.
* When no published version fixes every known vulnerability yet, the source is **skipped** rather
than failed, and the pipeline does not update anything.

Combine it with a source such as link:/docs/plugins/resource/pypi/["PyPI"] or
link:/docs/plugins/resource/github_release/["GitHub Release"] when you also want to stay on the
latest release: the <<Examples,condition example>> below takes the latest release and only writes it
when it has no known vulnerability.

When the source feeds a target that changes something, the pipeline changelog, used in the pull
request description, lists the vulnerabilities fixed between the two versions, along with the latest
published version of the package, as recorded by link:https://deps.dev[deps.dev]. The changelog is
best effort: a failing lookup leaves it out without failing the pipeline.

== Limitations

* The answers are only as good as osv.dev and deps.dev, which index published releases with some
delay. A very recent release may be missing, or not yet linked to an advisory.
* The resource needs network access to the OSV API, or to the mirror set in `url`.
* No authentication is supported.

== Examples

=== Raise a package to its fixed version

The source returns `3.1.6`, the lowest `jinja2` release that fixes the five known vulnerabilities of
`3.1.2`, and the target pins it in `requirements.txt`.

[source,yaml]
----
# updatecli.yaml
{{<include "assets/code_example/docs/plugins/resources/vulnerability_osv/updatecli.d/fixedversion.yaml">}}
----

=== Only bump to a version without known vulnerabilities

The source takes the latest `requests` release from PyPI, and the condition only lets the target run
when that release has no known vulnerability rated `HIGH` or `CRITICAL`.

[source,yaml]
----
# updatecli.yaml
{{<include "assets/code_example/docs/plugins/resources/vulnerability_osv/updatecli.d/condition.yaml">}}
----

When the condition fails, it lists what it found:

[source,text]
----
✗ 5 known vulnerabilities for version "3.1.2" of PyPI package "jinja2":
  * GHSA-cpwx-vrp4-4pq7 (CVE-2025-27516, PYSEC-2026-1471) [MODERATE] Jinja2 vulnerable to sandbox breakout through attr filter selecting format method - fixed in: 3.1.6
  * GHSA-gmj6-6f8f-6699 (CVE-2024-56201, PYSEC-2026-1472) [MODERATE] Jinja has a sandbox breakout through malicious filenames - fixed in: 3.1.5
  ...
----

=== List the known vulnerabilities

With `key: ids`, the source returns the IDs of the known vulnerabilities, here for the Go module
`golang.org/x/net` at `v0.20.0`, with one of them ignored.

[source,yaml]
----
# updatecli.yaml
{{<include "assets/code_example/docs/plugins/resources/vulnerability_osv/updatecli.d/ids.yaml">}}
----

== Links

* link:https://osv.dev[OSV, the open source vulnerability database]
* link:https://google.github.io/osv.dev/api/[OSV API]
* link:https://osv-vulnerabilities.storage.googleapis.com/ecosystems.txt[Ecosystems published by osv.dev]
* link:https://github.com/package-url/purl-spec[Package URL specification]
* link:https://deps.dev[deps.dev]
