How OSV-Scanner Groups Related Vulnerabilities and Manages Aliases

OSV-Scanner groups related vulnerabilities by converting OSV records into ID-alias representations, detecting intersecting alias sets between entries, and merging them into deterministic groups using a union-find-like algorithm.

The google/osv-scanner repository implements a sophisticated grouping mechanism to ensure that every vulnerability representing the same underlying issue—whether identified by CVE, GHSA, USN, or other ID schemes—appears as a unified entry in scan results. This process prevents duplicate reporting while preserving the complete alias chain for downstream formatters. Understanding how OSV-Scanner groups related vulnerabilities and manages aliases is essential for interpreting scan outputs across JSON, SARIF, and CycloneDX formats.

The Three-Stage Grouping Pipeline

The grouping logic resides in internal/grouper/ and executes in three distinct stages to consolidate vulnerability aliases into logical groups.

Stage 1: Converting Raw OSV Data to ID-Alias Pairs

First, the scanner transforms each osvschema.Vulnerability into a simplified IDAliases struct. This struct captures the primary vulnerability ID and every known alias associated with the record.

In internal/grouper/grouper_models.go, the ConvertVulnerabilityToIDAliases function (lines 16-27) extracts the primary ID via v.GetId() and compiles the complete alias list, including upstream IDs and USN-related CVEs:

import (
    "github.com/google/osv-scanner/v2/internal/grouper"
    "github.com/ossf/osv-schema/bindings/go/osvschema"
)

// ConvertVulnerabilityToIDAliases extracts primary IDs and aliases from OSV records.
func extractAliases(vulns []*osvschema.Vulnerability) []grouper.IDAliases {
    return grouper.ConvertVulnerabilityToIDAliases(vulns)
}

Stage 2: Detecting Intersecting Alias Sets

Two vulnerabilities are considered related if their alias sets intersect or if one primary ID appears in the other's alias list. The hasAliasIntersection function in internal/grouper/grouper.go (lines 13-22) implements this comparison logic by checking if any alias from the first entry exists in the second entry's slice, or vice versa.

Stage 3: Merging into Deterministic Groups

The Group function in internal/grouper/grouper.go (lines 24-73) performs the final consolidation using a union-find-like algorithm. It walks the list pair-wise, merging groups whenever an intersection is detected.

After merging, the function:

  • Extracts final groups from the union structure
  • Adds primary IDs to the alias list for completeness
  • Deduplicates entries
  • Sorts results for deterministic output
import "github.com/google/osv-scanner/v2/internal/grouper"

// Group consolidates ID-alias pairs into merged vulnerability groups.
func createGroups(vulns []*osvschema.Vulnerability) []models.GroupInfo {
    idAliases := grouper.ConvertVulnerabilityToIDAliases(vulns)
    return grouper.Group(idAliases)
}

Data Structures and Group Access

The grouping results are stored in models.GroupInfo, defined in pkg/models/results.go. This struct contains two key fields:

  • IDs: The primary vulnerability IDs belonging to the group (always sorted)
  • Aliases: All IDs referring to the same issue, including original IDs and collected aliases

When vulnerability results are flattened via VulnerabilityResults.Flatten(), the getGroupInfoForVuln helper (lines 77-80 in pkg/models/results.go) retrieves the appropriate group for each vulnerability ID:

// getGroupInfoForVuln returns the GroupInfo containing a specific vulnerability ID.
func groupForVuln(pkg models.PackageVulns, vulnID string) models.GroupInfo {
    return getGroupInfoForVuln(pkg.Groups, vulnID)
}

Practical Implementation in Output Formatters

To apply grouping to a package's vulnerabilities, OSV-Scanner invokes the grouper during result construction in pkg/osvscanner/vulnerability_result.go:

pkg.Groups = grouper.Group(
    grouper.ConvertVulnerabilityToIDAliases(pkg.Vulnerabilities),
)

Downstream formatters access these groups to display alias information. For example, the SARIF formatter in internal/output/sarif.go (lines 30-56) generates an "also known as" line when multiple aliases exist:

// Template logic for SARIF output
{{- if gt (len .AliasedVulns) 1 }}
(Also published as: {{range .AliasedVulns -}} {{if ne .ID $.ID -}} [{{.ID}}](https://osv.dev/{{.ID}}), {{end}}{{end}})
{{- end }}

Summary

  • OSV-Scanner groups related vulnerabilities through a three-stage pipeline: ID-alias conversion, intersection detection, and union-find merging as implemented in internal/grouper/grouper.go.
  • Alias management uses the IDAliases struct to track primary IDs alongside CVE, GHSA, and USN aliases from internal/grouper/grouper_models.go.
  • Deterministic output is ensured by sorting and deduplicating groups in the Group function before attaching results to PackageVulns records.
  • Downstream formatters access grouped aliases via models.GroupInfo and getGroupInfoForVuln to display unified vulnerability reports across JSON, SARIF, and CycloneDX outputs.
  • Intersection logic in hasAliasIntersection identifies related vulnerabilities when primary IDs appear in alias lists or when alias sets overlap.

Frequently Asked Questions

How does OSV-Scanner determine if two vulnerabilities are the same issue?

OSV-Scanner considers two vulnerabilities related if their alias sets intersect or if one's primary ID appears in the other's alias list. The hasAliasIntersection function in internal/grouper/grouper.go (lines 13-22) performs this check by comparing the IDAliases slices generated from raw OSV data.

What information is contained in a vulnerability group?

Each group stores complete alias chains in the GroupInfo struct defined in pkg/models/results.go. The IDs field contains primary vulnerability identifiers, while the Aliases field includes every associated CVE, GHSA, USN, and upstream ID referring to the same security issue.

Where does the grouping logic execute in the scan process?

The grouping occurs after vulnerability detection but before output formatting. Specifically, pkg/osvscanner/vulnerability_result.go calls grouper.ConvertVulnerabilityToIDAliases followed by grouper.Group to populate the Groups field on each PackageVulns record before results are flattened for display.

How does OSV-Scanner prevent duplicate vulnerability entries?

By merging intersecting alias sets into single GroupInfo entries, OSV-Scanner ensures that CVEs, GHSA advisories, and distribution-specific IDs (like USNs) referring to the same flaw appear as one logical vulnerability. The deduplication happens during the union-find merge phase in internal/grouper/grouper.go (lines 24-73).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →