osv

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package osv resolves vulnerability advisories for a package version from the OSV database (https://osv.dev).

An advisory is keyed by every identifier it is known by (its own OSV id plus every alias, such as CVE- and GHSA- ids), and the record that actually carries package-level detail wins when the same key is contributed by more than one source record.

The Go database is the only one that publishes vulnerable import paths, so Advisory.Pkgs is populated for Go refs and left empty for everything else -- see advisoryFor.

Index

Constants

View Source
const DefaultBaseURL = "https://api.osv.dev/v1"

DefaultBaseURL is the OSV v1 API root. Endpoints are derived from it.

View Source
const GoEcosystem = "Go"

GoEcosystem is the OSV ecosystem name for Go modules and the standard library.

Variables

View Source
var ErrUnknownDistro = errors.New("no OSV ecosystem is known for this distribution")

ErrUnknownDistro is returned by Release.Ecosystem when the distribution has no entry in the mapping table. It is deliberately an error and never an empty string: a missing ecosystem must stop the scan, because an OSV query with no ecosystem finds nothing and reads exactly like a clean image.

Functions

func Families

func Families() []string

Families lists the OSV ecosystem families Ecosystem can produce, without their version suffixes. It is what --ecosystem accepts for OS packages, and it lives beside the mapping so the two cannot drift apart.

func KnownDistroIDs

func KnownDistroIDs() []string

KnownDistroIDs lists the os-release ID values Ecosystem can map, for error messages.

func MatchesProductRelease

func MatchesProductRelease(eco, release string) bool

MatchesProductRelease reports whether an OSV affected-entry ecosystem string names a product of the given release.

eco is the full affected-entry spelling ("SUSE:Linux Enterprise Module for Basesystem 15 SP7"); release is a ProductRelease token ("15 SP7"). The support-phase suffix is dropped before comparing, because it distinguishes subscriptions rather than releases: an image running 15 SP4 is described by the "15 SP4-LTSS" records whether or not its owner holds that subscription.

Types

type Advisory

type Advisory struct {
	// ID is the canonical OSV identifier (e.g. GO-2024-1234, DSA-5678-1).
	ID string
	// Aliases are all other identifiers this advisory is known by.
	Aliases []string
	// Upstream is the vulnerabilities this record addresses, which for a distro
	// advisory is the CVEs its patch fixes.
	//
	// It is deliberately not merged into Aliases, because an alias is a claim
	// of identity and this is not one. Every distro database uses this field
	// and none uses aliases: SUSE-SU-2026:0312-1 addresses eight unrelated
	// CVEs, RHSA-2024:2447 seven. Treating those as eight names for one thing
	// would let buildMap file the record under eight keys and borrowSeverity
	// copy one CVE's vector onto the other seven.
	//
	// Consumers that join on CVE read it anyway, because a bundle still has to
	// be findable by what it fixes -- see advisoryResolver.cveSets.
	Upstream []string
	// Summary and Details are the advisory prose. They are the input to
	// advisory-text mining for ecosystems that publish no package-level data.
	Summary string
	Details string
	// Pkgs is the set of vulnerable import paths declared for a Go module.
	// Empty when OSV publishes no import paths (e.g. GitHub-only GHSA
	// records), in which case callers should fall back to module granularity,
	// and always empty for non-Go ecosystems.
	Pkgs []string

	// CVSSVector is the CVSS:3.0 or CVSS:3.1 base vector the record publishes,
	// empty when it publishes none or publishes only a version this tool does
	// not score. It is kept as the string rather than only as a number so a
	// report can show the metrics behind a rating someone disputes.
	CVSSVector string
	// PublisherSeverity is the qualitative rating the database itself assigned,
	// verbatim. Empty when the record carries no label.
	//
	// It is kept separate from CVSSVector because the two are independent
	// claims that disagree more often than one would expect. See Severity.
	PublisherSeverity string
}

Advisory is the resolved information for a single vulnerability id.

func (*Advisory) CVSSScore added in v0.2.0

func (a *Advisory) CVSSScore() (float64, bool)

CVSSScore returns the base score for the advisory's vector. The bool is false when there is no vector, or it is a version this tool does not score, and callers must not read that as a score of zero -- 0.0 is a real CVSS answer.

func (*Advisory) Severity added in v0.2.0

func (a *Advisory) Severity() string

Severity is the rating to display for this advisory: the more severe of what the publisher said and what its CVSS v3 vector computes to.

Taking the maximum is not indecision about a conflict. It is the only rule available here that never demotes a finding on a metadata technicality. The two sources disagree in both directions -- measured across 442 GHSA records, the v3 vector is milder than GitHub's own label 27 times and harsher 20 times -- so neither "always trust the vector" nor "always trust the label" avoids quietly lowering the severity of some real findings.

Neither source is wrong. GitHub rates the advisory, increasingly against the CVSS 4.0 vector it also publishes and this tool deliberately does not score, while the v3 vector is a separate and older statement about the same flaw. The computed score cannot simply be dropped in the label's favour either: a Debian record carries a vector and no label at all, and scoring it is what makes it comparable with a GHSA one in the same table.

Erring upward costs a reader time on a finding milder than billed. Erring downward costs them the finding.

type Client

type Client struct {
	HTTP *http.Client
	// BaseURL is the API root; DefaultBaseURL when empty.
	BaseURL string
	// Concurrency bounds the parallel per-id fetches QueryBatch makes;
	// defaultConcurrency when zero.
	Concurrency int
}

Client queries the OSV API.

func NewClient

func NewClient() *Client

NewClient returns a Client with sane defaults.

func (*Client) Query

func (c *Client) Query(ctx context.Context, ref Ref) (map[string]*Advisory, error)

Query returns the map of advisory-id -> Advisory for ref. Every alias identifier is a key in the returned map, so a caller may look up a CVE, GHSA or GO id interchangeably.

func (*Client) QueryBatch

func (c *Client) QueryBatch(ctx context.Context, refs []Ref) ([]map[string]*Advisory, error)

QueryBatch resolves many refs at once. result[i] is what Query(refs[i]) would have returned, so the answer is always the same length as refs.

A whole-image scan is thousands of lookups; one /v1/query round trip each is not viable. /v1/querybatch takes 1000 refs per request but answers with ids only, so every distinct id is then fetched once through /v1/vulns/{id} -- distinct being the point, since an OS advisory typically covers many of the packages in one image.

func (*Client) Vuln

func (c *Client) Vuln(ctx context.Context, id string) (*Advisory, error)

Vuln fetches one advisory record by its OSV id.

type Ref

type Ref struct {
	// Ecosystem is an OSV ecosystem string, e.g. "Go", "Debian:12",
	// "Alpine:v3.19". See Release.Ecosystem for how these are derived for OS
	// distributions.
	Ecosystem string
	Name      string
	Version   string

	// Release narrows a bare-family ecosystem to a single product release. It
	// is empty for every ecosystem whose query already names its release, and
	// when set an advisory survives only if one of its affected entries names
	// a product of that release. See Release.ProductRelease for why SUSE
	// cannot be handled in the query itself.
	Release string
}

Ref is an OSV package coordinate: an ecosystem name, a package name as that ecosystem's database spells it, and a version. Version may be empty to ask for every advisory against the package regardless of version.

func (Ref) String

func (r Ref) String() string

type Release

type Release struct {
	ID              string   // ID=
	IDLike          []string // ID_LIKE=
	Version         string   // VERSION=
	VersionID       string   // VERSION_ID=
	VersionCodename string   // VERSION_CODENAME=
	PrettyName      string   // PRETTY_NAME=
	CPEName         string   // CPE_NAME=
}

Release is the subset of /etc/os-release needed to name an OSV ecosystem.

func ParseOSRelease

func ParseOSRelease(r io.Reader) (Release, error)

ParseOSRelease reads the os-release(5) key=value format.

func (Release) Ecosystem

func (rel Release) Ecosystem() (string, error)

Ecosystem returns the OSV ecosystem string for this release.

The strings below were verified against the live api.osv.dev rather than read off the schema, because the API validates them asymmetrically: the family name is checked -- a misspelled "Debain:12" is rejected with HTTP 400 and {"code":3,"message":"invalid ecosystem"} -- but the version suffix is not. "Debian:99" answers HTTP 200 with an empty result, indistinguishable from a clean image. That asymmetry is why this is a table with tests rather than a format string, and why an unrecognized distribution is an error.

The suffix rules are not uniform and none of them are guessable:

Debian          major only            Debian:12          (bare "Debian" over-matches every release)
Ubuntu          ":LTS" only when LTS  Ubuntu:24.04:LTS   ("Ubuntu:24.10" for a non-LTS release)
Alpine          "v" prefix, no patch  Alpine:v3.19       ("Alpine:3.19" finds nothing)
Azure Linux     major only            Azure Linux:3      ("Azure Linux:3.0" finds nothing)
openEuler       "-LTS" when LTS       openEuler:24.03-LTS
openSUSE        product name from PRETTY_NAME
Red Hat         bare -- see below
SLE             bare + ProductRelease narrowing -- see below

func (Release) ProductRelease

func (rel Release) ProductRelease() string

ProductRelease is the release token an affected entry must carry for this image, or "" when no narrowing applies.

It exists for the bare-family ecosystems, where a query matches records from every product the vendor ships. That over-matching is not benign for SUSE: gzip is fixed at 1.10-150200.13.1 on SLE 15 and at 1.13-160000.3.1 on SLE 16, so a *fully patched* SLES 15 SP7 image still matches the SLE 16 record, and no amount of patching will ever clear it. Version comparison cannot sort this out, because the two products' version lines never converge.

The token is the trailing version part of the product name -- "15 SP7" for SLES 15 SP7, "5.5" for SLE Micro 5.5 -- which is the one component every spelling of a product shares, module and support-phase suffix included.

type StatusError

type StatusError struct {
	Status int
	URL    string
	Body   string
}

StatusError is a non-200 answer from the OSV API.

func (*StatusError) Error

func (e *StatusError) Error() string

func (*StatusError) Retryable

func (e *StatusError) Retryable() bool

Retryable reports whether repeating the request could plausibly succeed.

A 4xx other than 429 is a defect in the request, not a transient fault. The one that matters here is an unrecognized ecosystem name: OSV answers {"code":3,"message":"invalid ecosystem"} with HTTP 400. Retrying that three times and then reporting "unexpected status 400" buries the one message that says what is wrong.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL