vex

package
v0.8.2 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package vex reads OpenVEX documents from a VEX Hub repository and matches their statements against findings.

A VEX hub is a vendor saying, in public and in a machine-readable form, "we looked at this CVE in this product and here is what we concluded". vexscan reaches its own conclusions from local evidence; a hub says whether someone with more context has already answered the same question. The two are kept apart on purpose -- nothing in this package sets a status.

The shape of a hub, as published:

vex-repository.json          the repository's own metadata
index.json                   product purl -> path of that product's document
pkg/<type>/<path>/scan.openvex.json

The unit of a document is a statement about a (vulnerability, product) pair, where the product is the shipped artifact -- a container image, a Go main module -- and the optional subcomponents are the dependencies inside it that the vulnerability is actually filed against. A vexscan finding is one of those subcomponents, so matching means agreeing on the product, the vulnerability and the subcomponent all three.

Index

Constants

View Source
const (
	StatusNotAffected        = "not_affected"
	StatusAffected           = "affected"
	StatusFixed              = "fixed"
	StatusUnderInvestigation = "under_investigation"
)

VEX statuses, as OpenVEX defines them.

Variables

This section is empty.

Functions

func Exculpatory

func Exculpatory(status string) bool

Exculpatory reports whether a status says the reader has nothing to do.

not_affected and fixed answer the question; affected and under_investigation are the vendor agreeing there is one, or admitting they do not know yet, and neither should make a finding quieter.

func GoProduct

func GoProduct(mainModule string) string

GoProduct turns a Go main module path into its product purl.

No escaping is applied: a hub's golang keys are written as the plain module path, upper-case letters and all (pkg:golang/github.com/Altinity/...), which is also exactly what build info reports.

func ImageProduct

func ImageProduct(ref string) string

ImageProduct turns a container image reference into the product purl a VEX hub indexes it under.

rancher/hardened-kubernetes:v1.30.1
  -> pkg:oci/hardened-kubernetes?repository_url=index.docker.io/rancher/hardened-kubernetes

The normalization is Docker's, because that is what the hub's keys were written with: a bare name is under library/, a missing registry is index.docker.io, and docker.io is spelled index.docker.io. The tag and digest are dropped -- a hub keys a repository, not a release, and the statements inside carry their own timestamps.

Returns "" for a reference it cannot make sense of, which the caller should treat as "no product to look up" rather than as an error.

Types

type Doc

type Doc struct {
	Author     string
	Timestamp  string
	Statements []Statement
}

Doc is one OpenVEX document.

func ParseDoc

func ParseDoc(b []byte) (*Doc, error)

ParseDoc decodes an OpenVEX document.

A statement naming no vulnerability or no product cannot be matched against anything, so it is dropped rather than carried as an entry that silently never fires.

type Hub

type Hub struct {
	// URL is the hub as the user named it, for messages and for the record in
	// the JSON output.
	URL string

	HTTP *http.Client
	// contains filtered or unexported fields
}

Hub is one opened VEX repository: its index, plus whatever documents have been fetched out of it so far.

A Hub is safe for concurrent use. Lookup is the only method, and two goroutines asking for the same product will each fetch it -- the cache deduplicates the answer, not the request, which is the right trade for a handful of products per scan.

func Open

func Open(ctx context.Context, location string) (*Hub, error)

Open fetches a hub's index.

The location may be a GitHub repository URL, a raw base URL, or a local directory. A GitHub URL is rewritten to raw.githubusercontent.com, which is what makes `--vexhub https://github.com/rancher/vexhub` -- the thing a reader would paste -- work without them having to know the raw form.

A deliberate deviation from the VEX Repository spec: the spec distributes a hub as the tarball named in vex-repository.json. rancher/vexhub's index is 208 KB and a scan needs one or two documents out of it, against a ~30 MB archive. Fetching the index and then only the documents actually wanted is dramatically cheaper. The tarball transport is worth adding if a hub turns up that does not serve its files individually; none does today.

func (*Hub) IndexRaw added in v0.8.0

func (h *Hub) IndexRaw() []byte

IndexRaw is index.json as the hub published it.

func (*Hub) Location added in v0.8.0

func (h *Hub) Location(product string) (string, bool)

Location is where the hub files a product's document, relative to its root, matching the product the same way Lookup does.

func (*Hub) Lookup

func (h *Hub) Lookup(ctx context.Context, product string) (*Doc, bool, error)

Lookup returns the document covering a product. The bool is false when the hub has no document for it, which is the ordinary case and not an error -- a hub covers one vendor's artifacts, and most scans are of something else.

func (*Hub) Raw added in v0.8.0

func (h *Hub) Raw(ctx context.Context, loc string) ([]byte, bool, error)

Raw reads one file out of the hub verbatim, by a location the index gave.

It exists for the writer in internal/vexpr, which merges into an existing document and must reproduce every field OpenVEX allows -- including the ones Doc does not model. Lookup's parsed form cannot do that, so this returns the bytes. A file the hub does not have is ok=false rather than an error: the caller's next step for "no document yet" is to start one.

func (*Hub) Size

func (h *Hub) Size() int

Size reports how many products the hub indexes, for the log line that tells a reader the hub was actually read.

type Product

type Product struct {
	ID            string
	Subcomponents []string
}

Product is one artifact a statement covers, and optionally the components inside it the vulnerability belongs to.

An entry with no subcomponents covers the whole product. That is a stronger claim than a subcomponent-scoped one and is treated as such when two statements compete.

type Statement

type Statement struct {
	// Vulnerability is the id the vendor filed the statement under. It is not
	// always a CVE: a SUSE-keyed hub writes advisory ids like
	// "SUSE-RU-2026:1228-1", which OSV also publishes, so they still match.
	Vulnerability string
	Aliases       []string
	Products      []Product

	Status        string
	Justification string
	// ImpactStatement is the vendor's own sentence explaining the conclusion,
	// and is usually the single most useful field in the document.
	ImpactStatement string
	ActionStatement string
	Timestamp       string
}

Statement is one claim about one vulnerability in one product.

func Match

func Match(d *Doc, product string, ids []string, subPURL string) (*Statement, string)

Match finds the statement in d that speaks to a finding: one naming any of ids as its vulnerability, under the given product, covering subPURL.

It returns the statement and a note describing every spelling disagreement it had to tolerate to get there, or (nil, "") if nothing matched. The note is not an aside -- it is the audit trail for a deliberately loose comparison, and belongs in the evidence the caller records.

type StatusError

type StatusError struct {
	Status int
	URL    string
}

StatusError is a non-200 answer from a hub.

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 404 is the common case here -- a hub that simply has no document for this product -- and retrying it three times only makes the scan slower.

Jump to

Keyboard shortcuts

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