# How OSV-Scanner Groups Related Vulnerabilities and Manages Aliases

> Discover how OSV-Scanner groups related vulnerabilities by converting OSV records to ID-alias representations and merging them using a union-find algorithm. Learn alias management.

- Repository: [Google/osv-scanner](https://github.com/google/osv-scanner)
- Tags: internals
- Published: 2026-04-25

---

**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`](https://github.com/google/osv-scanner/blob/main/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:

```go
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`](https://github.com/google/osv-scanner/blob/main/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`](https://github.com/google/osv-scanner/blob/main/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

```go
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`](https://github.com/google/osv-scanner/blob/main/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`](https://github.com/google/osv-scanner/blob/main/pkg/models/results.go)) retrieves the appropriate group for each vulnerability ID:

```go
// 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`](https://github.com/google/osv-scanner/blob/main/pkg/osvscanner/vulnerability_result.go):

```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`](https://github.com/google/osv-scanner/blob/main/internal/output/sarif.go) (lines 30-56) generates an "also known as" line when multiple aliases exist:

```go
// 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`](https://github.com/google/osv-scanner/blob/main/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`](https://github.com/google/osv-scanner/blob/main/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`](https://github.com/google/osv-scanner/blob/main/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`](https://github.com/google/osv-scanner/blob/main/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`](https://github.com/google/osv-scanner/blob/main/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`](https://github.com/google/osv-scanner/blob/main/internal/grouper/grouper.go) (lines 24-73).