evidence

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2026 License: AGPL-3.0, AGPL-3.0-only Imports: 5 Imported by: 0

Documentation

Overview

Package evidence is StageFreight's security-ENRICHMENT layer. The scanners (OSV, Trivy, Grype) DISCOVER vulnerabilities; this package ENRICHES each discovered vulnerability with evidence so policy can judge the full story — "Critical + reachable + KEV-listed + fixable" — instead of a bare severity.

The pipeline is:

Discovery (scanners) → Normalized Vulnerabilities → Enrichment (contributors) →
Findings (vuln + evidence) → Policy → Renderer

Reachability (govulncheck for Go) is deliberately just the FIRST evidence contributor, not the framework itself. KEV, EPSS, fix-availability, SBOM-ownership, exploit-maturity and runtime-loaded evidence all slot in behind the same EvidenceContributor seam without touching the pipeline. Evidence is DATA — a contributor never decides policy.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Confidence

type Confidence int

Confidence qualifies an analyzer's verdict without gating policy — it is DISCLOSED to the operator (govulncheck is High; an experimental Rust analyzer may be Medium/Experimental) so they know how much to trust a downgrade.

const (
	ConfidenceNone Confidence = iota
	ConfidenceExperimental
	ConfidenceMedium
	ConfidenceHigh
)

func ParseConfidence

func ParseConfidence(s string) Confidence

ParseConfidence is the inverse of String, for reconstructing from a persisted catalogue. Unrecognized input is ConfidenceNone.

func (Confidence) String

func (c Confidence) String() string

type Evidence

type Evidence interface {
	Kind() string // "reachability", "kev", "epss", …
}

Evidence is one enrichment fact about a vulnerability. It is DATA, never a verdict — policy alone interprets it. Each concrete kind (ReachabilityEvidence today; KEVEvidence, EPSSEvidence, FixEvidence… later) implements this.

type EvidenceContributor

type EvidenceContributor interface {
	Name() string
	Supports(ecosystem string) bool
	// Contribute returns evidence to attach, indexed by the vulnerability it enriches. A vuln
	// absent from the result simply gets no evidence from this contributor.
	Contribute(ctx context.Context, target Target, vulns []Vulnerability) (map[VulnRef]Evidence, error)
}

EvidenceContributor enriches findings for the ecosystems it supports. It is the pluggable seam: a Go reachability contributor (govulncheck), a Rust one (experimental), and ecosystem-agnostic ones (KEV, EPSS) all satisfy it. A contributor produces evidence; it never decides policy, and it must never fabricate evidence for something it did not examine.

type Finding

type Finding struct {
	Vuln     Vulnerability
	Evidence []Evidence
}

Finding is a discovered vulnerability plus the evidence contributors have attached.

func (Finding) Reachability

func (f Finding) Reachability() (ReachabilityEvidence, bool)

Reachability extracts the reachability evidence from a finding, if a contributor attached it.

type GoReachability

type GoReachability struct {
	// Run invokes govulncheck and returns its -json output for the module rooted at dir.
	// Injectable so the parser is unit-testable without the tool.
	Run func(ctx context.Context, dir string) ([]byte, error)
}

GoReachability is the govulncheck-backed reachability contributor for the "go" ecosystem — the first EvidenceContributor. govulncheck performs call-graph analysis over the built program, so it answers "can the vulnerable code be reached?", the precision layer over the module-level scanners. Go stdlib and toolchain advisories are covered too.

func NewGoReachability

func NewGoReachability() *GoReachability

NewGoReachability returns the contributor wired to the real govulncheck binary.

func (*GoReachability) Contribute

func (g *GoReachability) Contribute(ctx context.Context, target Target, vulns []Vulnerability) (map[VulnRef]Evidence, error)

Contribute runs govulncheck once and maps its findings onto the vulnerabilities being enriched. A vulnerability govulncheck did not report a finding for gets NO evidence — it stays Unknown (fail-closed), never fabricated as reachable or unreachable.

func (*GoReachability) Name

func (*GoReachability) Name() string

func (*GoReachability) Supports

func (*GoReachability) Supports(eco string) bool

type ReachabilityEvidence

type ReachabilityEvidence struct {
	State      ReachabilityState
	Analyzer   string // e.g. "govulncheck"
	Confidence Confidence
	Facts      []string // human-readable, e.g. "imported golang.org/x/crypto/ssh",

}

ReachabilityEvidence is the FIRST evidence kind: whether a vulnerability's code is actually reachable in this program's call graph. It answers the question a scanner can't — not "is a vulnerable module present?" but "can the vulnerable code be called?". Produced by a reachability contributor (Go → govulncheck; Rust → experimental).

Tri-state on purpose: "Unknown" (no analyzer for this ecosystem) is NOT "Unreachable". Collapsing them would silently hide vulnerabilities we simply could not analyze.

func (ReachabilityEvidence) Kind

Kind implements Evidence.

type ReachabilityState

type ReachabilityState int

ReachabilityState is the reachability verdict.

const (
	ReachUnknown     ReachabilityState = iota // no analyzer ran for this ecosystem
	ReachReachable                            // an analyzer proved a call path
	ReachUnreachable                          // an analyzer proved the code never enters the call graph
)

func ParseReachabilityState

func ParseReachabilityState(s string) ReachabilityState

ParseReachabilityState is the inverse of String — it reconstructs a state from its serialized form (e.g. when reading a persisted catalogue). Unrecognized input is ReachUnknown (the fail-closed default).

func (ReachabilityState) String

func (s ReachabilityState) String() string

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds the enabled contributors, run in registration order.

func NewRegistry

func NewRegistry(cs ...EvidenceContributor) *Registry

NewRegistry builds a registry from the given contributors.

func (*Registry) Enrich

func (r *Registry) Enrich(ctx context.Context, target Target, vulns []Vulnerability) []Finding

Enrich runs every contributor over the vulnerabilities it supports and returns Findings with evidence attached. It is fail-closed and monotonic: a missing contributor for an ecosystem, or a contributor that errors, simply adds NO evidence — nothing is downgraded on absent evidence. Enrichment can only ever ADD signal; absence leaves a finding at its scanner severity (see policy.go).

func (*Registry) Register

func (r *Registry) Register(c EvidenceContributor)

Register appends a contributor.

type Target

type Target struct {
	// EcosystemDir maps a normalized ecosystem to the module/source root its contributors
	// should examine. A KEV/EPSS contributor ignores it; a reachability contributor needs it.
	EcosystemDir map[string]string
}

Target carries what contributors need to do their work — e.g. a reachability contributor needs the source root (call-graph analysis needs source + a build). Extensible without changing the interface.

type VulnRef

type VulnRef struct {
	ID        string
	Ecosystem string
	Package   string
	Symbol    string
}

VulnRef is the stable identity used to index evidence back onto a vulnerability.

type Vulnerability

type Vulnerability struct {
	ID        string   // the discovering scanner's identifier, e.g. "GO-2026-5932", "CVE-2026-1234"
	Aliases   []string // other identifiers for the same advisory, e.g. ["CVE-…", "GHSA-…"]
	Ecosystem string   // normalized: "go", "rust", "npm", "oci", …
	Package   string   // AFFECTED package (the reachability join key), e.g. "golang.org/x/crypto/openpgp"
	Symbol    string   // optional affected symbol
	Severity  string   // "CRITICAL" | "HIGH" | "MODERATE" | "LOW" | "" (as the scanner reported)
	Source    string   // which scanner discovered it: "osv" | "trivy" | "grype"
}

Vulnerability is a normalized finding from the discovery layer — the join identity every contributor enriches. Kept minimal on purpose: each scanner maps its native output into this.

ID is the identifier the discovering scanner used, but it is NOT the correlation contract: the same underlying vulnerability carries several identifiers (CVE, GHSA, GO advisory), and a contributor may report under a different one than discovery did. Aliases holds the other known identifiers (OSV publishes them), and contributors correlate against Identifiers() — the full set — never a single scanner-specific ID.

func (Vulnerability) Identifiers

func (v Vulnerability) Identifiers() []string

Identifiers returns every identifier this vulnerability is known by — its primary ID plus any aliases. Contributors correlate their findings against this set so a CVE-keyed discovery still joins a GO-advisory-keyed analyzer (and vice versa), without OSV-ID ever being the contract.

func (Vulnerability) Ref

func (v Vulnerability) Ref() VulnRef

Ref is the join key for a vulnerability.

Jump to

Keyboard shortcuts

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