Skip to content

🎯 Reachability

The reachability module assesses how directly a vulnerability can be reached through the dependency graph — from a directly-used API (direct) down to not_reachable. It defines the ReachabilityLevel type and its constants, the ReachabilityResult and ReachabilitySummary structs, a pluggable ReachabilityAnalyzer interface, the default DependencyGraphReachabilityAnalyzer implementation, and batch/summary helpers.

Type: ReachabilityLevel

go
type ReachabilityLevel string

Describes how directly a vulnerability can be reached.

Constants

go
const (
    ReachabilityDirect       ReachabilityLevel = "direct"
    ReachabilityTransitive   ReachabilityLevel = "transitive"
    ReachabilityConditional  ReachabilityLevel = "conditional"
    ReachabilityNotReachable ReachabilityLevel = "not_reachable"
    ReachabilityUnknown      ReachabilityLevel = "unknown"
)
ConstantValueDescription
ReachabilityDirectdirectThe vulnerability is in a directly-used API
ReachabilityTransitivetransitiveThe vulnerability is in a transitive dependency
ReachabilityConditionalconditionalReachable only under certain conditions
ReachabilityNotReachablenot_reachableThe vulnerability cannot be reached
ReachabilityUnknownunknownReachability cannot be determined

Type: ReachabilityResult

go
type ReachabilityResult struct {
    Vulnerability *VulnerabilityFinding `json:"vulnerability"`
    Level         ReachabilityLevel     `json:"level"`
    Path          []string              `json:"path,omitempty"`
    Evidence      string                `json:"evidence,omitempty"`
    Confidence    float64               `json:"confidence"`
}
FieldTypeDescription
Vulnerability*VulnerabilityFindingThe vulnerability finding being analyzed
LevelReachabilityLevelThe reachability assessment
Path[]stringCall/dependency path from root to the vulnerable component
EvidencestringHow the reachability was determined
Confidencefloat64Confidence in the assessment (0.01.0)

Type: ReachabilityAnalyzer

go
type ReachabilityAnalyzer interface {
    Analyze(graph *DependencyGraph, findings []*VulnerabilityFinding) ([]*ReachabilityResult, error)
    AnalyzeComponent(graph *DependencyGraph, component *SBOMComponent, findings []*VulnerabilityFinding) ([]*ReachabilityResult, error)
}

A pluggable interface for reachability analysis, allowing implementations ranging from simple dependency-graph traversal to full static analysis.

Type: DependencyGraphReachabilityAnalyzer

go
type DependencyGraphReachabilityAnalyzer struct {
    MaxDepth               int
    IncludeDevDependencies bool
}

The default, lightweight analyzer suitable for most SCA use cases. It determines reachability from whether a component is a direct or transitive dependency.

FieldTypeDescription
MaxDepthintMaximum dependency depth to analyze (0 = unlimited)
IncludeDevDependenciesboolWhether to include development dependencies

Type: ReachabilitySummary

go
type ReachabilitySummary struct {
    Total           int    `json:"total"`
    Direct          int    `json:"direct"`
    Transitive      int    `json:"transitive"`
    Conditional     int    `json:"conditional"`
    NotReachable    int    `json:"notReachable"`
    Unknown         int    `json:"unknown"`
    HighestRiskLevel string `json:"highestRiskLevel"`
}
FieldTypeDescription
TotalintTotal vulnerabilities analyzed
DirectintCount of directly reachable vulnerabilities
TransitiveintCount of transitively reachable vulnerabilities
ConditionalintCount of conditionally reachable vulnerabilities
NotReachableintCount of unreachable vulnerabilities
UnknownintCount of vulnerabilities with unknown reachability
HighestRiskLevelstringHighest risk reachability level with count > 0

🆕 NewDependencyGraphReachabilityAnalyzer

go
func NewDependencyGraphReachabilityAnalyzer() *DependencyGraphReachabilityAnalyzer

Creates a default analyzer with unlimited depth (MaxDepth == 0) and dev dependencies excluded.

ReturnTypeDescription
#1*DependencyGraphReachabilityAnalyzerA new default analyzer
go
a := cpeskills.NewDependencyGraphReachabilityAnalyzer()

🔬 Analyze

go
func (a *DependencyGraphReachabilityAnalyzer) Analyze(graph *DependencyGraph, findings []*VulnerabilityFinding) ([]*ReachabilityResult, error)

Analyzes reachability for all findings against the dependency graph. Depths are recomputed first. Each finding is matched to a graph node by CVE CPE or OSV package name and assessed. Unmatched findings get ReachabilityUnknown.

ParameterTypeDescription
graph*DependencyGraphThe dependency graph
findings[]*VulnerabilityFindingThe vulnerability findings to assess
ReturnTypeDescription
#1[]*ReachabilityResultReachability result per finding
#2errorNon-nil if graph is nil
go
results, err := analyzer.Analyze(graph, findings)
if err != nil {
    log.Fatal(err)
}
for _, r := range results {
    fmt.Println(r.Level, r.Confidence)
}

🔬 AnalyzeComponent

go
func (a *DependencyGraphReachabilityAnalyzer) AnalyzeComponent(graph *DependencyGraph, component *SBOMComponent, findings []*VulnerabilityFinding) ([]*ReachabilityResult, error)

Analyzes reachability for the findings against a single component's node in the graph.

ParameterTypeDescription
graph*DependencyGraphThe dependency graph
component*SBOMComponentThe component to assess
findings[]*VulnerabilityFindingThe vulnerability findings to assess
ReturnTypeDescription
#1[]*ReachabilityResultReachability result per finding
#2errorNon-nil if graph is nil or the component is not in the graph
go
results, err := analyzer.AnalyzeComponent(graph, comp, findings)

⚡ QuickReachabilityCheck

go
func QuickReachabilityCheck(graph *DependencyGraph, component *SBOMComponent, finding *VulnerabilityFinding) *ReachabilityResult

A convenience function that runs the default analyzer on a single component/finding. On any error it returns a result with ReachabilityUnknown and Confidence == 0.0.

ParameterTypeDescription
graph*DependencyGraphThe dependency graph
component*SBOMComponentThe component to assess
finding*VulnerabilityFindingThe single finding to assess
ReturnTypeDescription
#1*ReachabilityResultThe reachability result (never nil)
go
r := cpeskills.QuickReachabilityCheck(graph, comp, finding)
fmt.Println(r.Level, r.Evidence)

📦 BatchReachabilityAnalysis

go
func BatchReachabilityAnalysis(graphs []*DependencyGraph, findings []*VulnerabilityFinding) (map[string][]*ReachabilityResult, error)

Runs reachability analysis across multiple dependency graphs — useful for monorepos where each sub-project has its own graph. Result keys are derived from the first direct root node's component name, falling back to graph-<index>.

ParameterTypeDescription
graphs[]*DependencyGraphThe dependency graphs to analyze
findings[]*VulnerabilityFindingThe vulnerability findings to assess
ReturnTypeDescription
#1map[string][]*ReachabilityResultMap of graph name to results
#2errorAlways nil; per-graph failures yield a nil entry
go
results, _ := cpeskills.BatchReachabilityAnalysis(graphs, findings)
for name, rs := range results {
    fmt.Println(name, len(rs))
}

📊 SummarizeReachability

go
func SummarizeReachability(results []*ReachabilityResult) *ReachabilitySummary

Aggregates reachability results into a summary, counting results by level and determining the highest risk level present.

ParameterTypeDescription
results[]*ReachabilityResultThe results to summarize
ReturnTypeDescription
#1*ReachabilitySummaryThe aggregated summary
go
summary := cpeskills.SummarizeReachability(results)
fmt.Printf("direct=%d transitive=%d highest=%s\n", summary.Direct, summary.Transitive, summary.HighestRiskLevel)

✅ GetActionableFindings

go
func GetActionableFindings(results []*ReachabilityResult) []*VulnerabilityFinding

Filters results to the actionable findings — those whose level is not ReachabilityNotReachable. (ReachabilityUnknown findings are retained since they warrant investigation.)

ParameterTypeDescription
results[]*ReachabilityResultThe results to filter
ReturnTypeDescription
#1[]*VulnerabilityFindingFindings that are not not_reachable
go
actionable := cpeskills.GetActionableFindings(results)
for _, f := range actionable {
    fmt.Println(f.CVE.CVEID)
}

📐 Reachability Levels Diagram

Released under the MIT License.