analyze

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package analyze orchestrates the vexscan pipeline: prepare a target (extract an image, open a rootfs, or check out a source tree), ask each ecosystem plugin what it finds, resolve advisories for what the plugins inventory, and optionally overlay an LLM assessment on the genuinely-affected results.

The division of labour is deliberate. Plugins own the *deterministic* question — is this vulnerable code present, and can it run — and nothing else. This package owns advisory resolution and the LLM overlay, so no plugin can make the model's opinion load-bearing.

Index

Constants

View Source
const (
	StatusNotPresent   = ecosystem.StatusNotPresent
	StatusNotInPath    = ecosystem.StatusNotInPath
	StatusLinked       = ecosystem.StatusLinked
	StatusReachable    = ecosystem.StatusReachable
	StatusUndetermined = ecosystem.StatusUndetermined
)
View Source
const SchemaVersion = 2

SchemaVersion is the version of the JSON Result shape.

1 was gomod-vex: Go only, one module per run. 2 adds the ecosystem-neutral finding identity, the per-ecosystem outcome list, and OS package findings. Every version-1 field is still present and still means what it meant.

Variables

This section is empty.

Functions

func Validate

func Validate(opts Options) error

Validate reports whether the options describe a coherent scan, touching neither the network nor the disk. It lets the caller tell a bad command line from a failed scan, and report the former before the pull rather than after.

It cannot catch everything: a bare --package name is resolved against the inventory, so whether it names anything is only knowable once the target has been read.

Types

type Finding

type Finding = ecosystem.Finding

The finding vocabulary lives in internal/ecosystem, which is what the plugins produce. These aliases keep the existing analyze.Finding / analyze.Status spelling working for callers and keep the JSON output byte-identical.

type InventoryResult

type InventoryResult struct {
	Target    string         `json:"target"`
	Mode      string         `json:"mode"` // "image" | "rootfs"
	OS        *OSInfo        `json:"os,omitempty"`
	Databases []pkgdb.Result `json:"databases"`

	// Unreadable is the part of the tree the walks could not enter. An
	// inventory that skipped a directory is a list of what is installed with
	// an unknown number of omissions, which is not the same document.
	Unreadable *target.Unreadable `json:"unreadable,omitempty"`

	// Languages are the installed distributions of the language ecosystems
	// that ship inside images: Python's site-packages, Node's node_modules.
	// They are kept separate from Databases because they overlap: Debian's
	// python3-yaml deb installs the same files a PyPI inventory reports under
	// "pyyaml", and merging the two would hide that both advisory namespaces
	// apply.
	Languages []langdb.Result `json:"languages,omitempty"`
}

InventoryResult is what a tree's package databases say is installed.

This is the raw material the OS ecosystem plugin works from, exposed on its own because it is checkable: a user who suspects a finding is wrong can see exactly which database row it came from, and the ecosystem string that will be used to query OSV before any query is made.

func Inventory

func Inventory(ctx context.Context, opts Options) (*InventoryResult, error)

Inventory reads the OS package databases of an image or a rootfs.

It deliberately does not require a subject: "what is in this tree" is a question worth answering on its own, and it is the one output that can be checked against `dpkg -l` or `rpm -qa` run inside the same tree.

func (*InventoryResult) LanguagePackages

func (r *InventoryResult) LanguagePackages() int

LanguagePackages counts the installed language distributions.

It is kept apart from Packages rather than added to it because the two overlap -- the same files can be one deb and one PyPI distribution -- so a single total would be a number that counts some code twice and means nothing.

func (*InventoryResult) Packages

func (r *InventoryResult) Packages() int

Packages counts the OS packages the inventory found.

type OSInfo

type OSInfo struct {
	ID         string `json:"id,omitempty"`
	VersionID  string `json:"version_id,omitempty"`
	PrettyName string `json:"pretty_name,omitempty"`

	// Ecosystem is the OSV ecosystem string, or empty with EcosystemError set.
	Ecosystem      string `json:"ecosystem,omitempty"`
	EcosystemError string `json:"ecosystem_error,omitempty"`
}

OSInfo is the distribution identity read from /etc/os-release.

type Options

type Options struct {
	Image string
	// RootFS is a filesystem tree already on disk -- an unpacked image, a
	// mounted volume, a machine's own /. It runs the image analyzers against a
	// tree nobody extracted, so it skips the pull but also arrives without an
	// image config: see runTree.
	RootFS string
	Repo   string // git repo (source mode); mutually exclusive with Image
	Ref    string // branch/tag/commit for Repo
	Path   string // module subdirectory within Repo (default ".")

	// Packages are the raw --package selectors: purls, ecosystem:name
	// shorthand, or bare names resolved against whatever inventory contains
	// them. See ecosystem.ParseSubject.
	Packages []string
	// Module is the deprecated --module flag, equivalent to one
	// --package golang:MODULE.
	Module string
	// All requests everything each plugin can inventory, rather than a named
	// list of packages.
	All bool
	// Ecosystems restricts which plugins run (--ecosystem). Empty runs them
	// all. Naming one nothing handles is an error, not an empty result.
	Ecosystems []string

	CVEs    []string // optional filter; empty means "every advisory that applies"
	Version string   // optional override of the detected module version (image mode)
	OS      string
	Arch    string

	// Roots are extra entrypoints for the reachability closures -- the OS
	// plugin's shared libraries and the language plugins' import graphs -- for
	// an image whose real command comes from outside its config.
	Roots []string
	// DlopenPolicy decides whether a reachable dlopen blocks conclusions.
	DlopenPolicy elfgraph.DlopenPolicy
	// DynamicPolicy decides whether a reachable import of a computed name
	// blocks conclusions. It is the import graph's DlopenPolicy.
	DynamicPolicy modgraph.DynamicPolicy
	// OSVEcosystem overrides the OSV ecosystem derived from the image's
	// os-release, for the distributions os-release does not determine. It is
	// not the same knob as Ecosystems, which chooses which plugins run.
	OSVEcosystem string

	// VEXHubs are VEX Hub repositories to check findings against (--vexhub),
	// in priority order: the first hub with a statement about a finding wins,
	// so an internal hub listed ahead of a vendor's overrides it.
	//
	// A statement never changes a finding's status. It records that someone has
	// already published an answer, which the report uses to decide what a
	// reader still has to look at.
	VEXHubs []string

	// GoVersion optionally pins the Go toolchain for repo-mode analysis
	// (e.g. "1.24.0"). Mainly useful with --module stdlib, whose findings depend
	// on the toolchain version.
	GoVersion string

	UseLLM bool
	// LLMEndpoint, LLMModel and LLMCommand say who to ask. Each falls back to
	// VEXSCAN_LLM_ENDPOINT / _MODEL / _COMMAND when empty, and exactly one of
	// endpoint and command must end up set: see llm.Config. The credential is
	// read from the environment only, never from here, so it cannot reach a
	// command line.
	LLMEndpoint string
	LLMModel    string
	LLMCommand  string

	// MineAdvisories lets the model read each advisory's prose for symbol,
	// soname and filename leads, which plugins then validate against the image.
	// Requires UseLLM.
	MineAdvisories bool
	// TrustImportAbsence lets the OS plugin conclude not_in_execute_path when
	// nothing the closure reaches imports the vulnerable symbol. Off by default:
	// the vulnerable function is usually called from inside the same library,
	// where no dynamic import records it.
	TrustImportAbsence bool

	// Logf receives progress messages (may be nil).
	Logf func(format string, args ...any)
}

Options configure a run. Set exactly one of Image, RootFS or Repo.

type Result

type Result struct {
	SchemaVersion int       `json:"schema_version"`
	Target        string    `json:"target"` // image ref, rootfs directory, or repo
	Mode          string    `json:"mode"`   // "image" | "rootfs" | "repo"
	Module        string    `json:"module"`
	Findings      []Finding `json:"findings"`

	// Ecosystems records how each plugin fared. It exists so a failure is
	// never indistinguishable from a clean result: a plugin that found a
	// package database and could not read it reports the error here and
	// contributes no findings at all.
	Ecosystems []ecosystem.EcosystemResult `json:"ecosystems,omitempty"`

	// Unreadable is the part of the target tree the scan could not enter,
	// accumulated across every plugin that walked it. It is nil in repo mode,
	// which analyzes a checkout the current user just created.
	//
	// It is set for the same reason it is recorded at all: a directory nothing
	// looked inside contributes no findings, which is exactly what a directory
	// full of nothing wrong contributes. Only one of those is good news.
	Unreadable *target.Unreadable `json:"unreadable,omitempty"`

	// VEXHubs records what each --vexhub contributed, including one that could
	// not be read. It is not part of Failed(): see vexOverlay for why a hub
	// failure is not the same kind of incompleteness as an ecosystem failure.
	VEXHubs []ecosystem.VEXHubResult `json:"vex_hubs,omitempty"`
}

Result is the full analysis output.

func Run

func Run(ctx context.Context, opts Options) (*Result, error)

Run dispatches to filesystem analysis -- an image or a rootfs -- or to source-repo analysis.

func (*Result) Failed

func (r *Result) Failed() bool

Failed reports whether the findings are an incomplete account of the target -- because an ecosystem could not complete, or because part of the tree could not be read.

type Status

type Status = ecosystem.Status

The finding vocabulary lives in internal/ecosystem, which is what the plugins produce. These aliases keep the existing analyze.Finding / analyze.Status spelling working for callers and keep the JSON output byte-identical.

Jump to

Keyboard shortcuts

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