Advisory Key Canonicalization Contract

Contract ID: CONTRACT-ADVISORY-KEY-001 Version: 1.0 Status: Published Last Updated: 2025-12-05

Overview

This contract defines how StellaOps canonicalizes advisory and vulnerability identifiers — CVE, GHSA, RHSA, DSA, USN, MSRC, and others — into a single stable key. A consistent canonical key lets VEX observations, policy findings, and risk assessments correlate reliably even when upstream sources name the same vulnerability in different formats.

Audience: developers integrating with Concelier/Excititor ingestion, VEX correlation, or any consumer that joins findings by advisory identifier.

Implementation Reference

Source: src/Concelier/__Libraries/StellaOps.Excititor.Core/Canonicalization/VexAdvisoryKeyCanonicalizer.cs

Data Model

VexCanonicalAdvisoryKey

The canonical advisory key structure returned by the canonicalizer.

public sealed record VexCanonicalAdvisoryKey
{
    /// <summary>
    /// The canonical advisory key used for correlation and storage.
    /// </summary>
    public string AdvisoryKey { get; }

    /// <summary>
    /// The scope/authority level of the advisory.
    /// </summary>
    public VexAdvisoryScope Scope { get; }

    /// <summary>
    /// Original and alias identifiers preserved for traceability.
    /// </summary>
    public ImmutableArray<VexAdvisoryLink> Links { get; }
}

Represents a link to an original or alias advisory identifier.

public sealed record VexAdvisoryLink
{
    /// <summary>
    /// The advisory identifier value.
    /// </summary>
    public string Identifier { get; }

    /// <summary>
    /// The type of identifier (cve, ghsa, rhsa, dsa, usn, msrc, other).
    /// </summary>
    public string Type { get; }

    /// <summary>
    /// True if this is the original identifier provided at ingest time.
    /// </summary>
    public bool IsOriginal { get; }
}

VexAdvisoryScope

The scope/authority level of an advisory.

ValueCodeDescriptionExamples
Global1Global identifiersCVE-2024-1234
Ecosystem2Ecosystem-specificGHSA-xxxx-xxxx-xxxx
Vendor3Vendor-specificRHSA-2024:1234, ADV-2024-1234
Distribution4Distribution-specificDSA-1234-1, USN-1234-1
Unknown0UnclassifiedCustom identifiers

Canonicalization Rules

Identifier Patterns

PatternRegexScopeType
CVE^CVE-\d{4}-\d{4,}$Globalcve
GHSA^GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$Ecosystemghsa
RHSA^RH[A-Z]{2}-\d{4}:\d+$Vendorrhsa
DSA^DSA-\d+(-\d+)?$Distributiondsa
USN^USN-\d+(-\d+)?$Distributionusn
MSRC^(ADV|CVE)-\d{4}-\d+$Vendormsrc
Other*Unknownother

Canonical Key Format

  1. CVE identifiers remain unchanged as they are globally authoritative:

    CVE-2024-1234 → CVE-2024-1234
    
  2. Non-CVE identifiers are prefixed with a scope indicator:

    GHSA-xxxx-xxxx-xxxx → ECO:GHSA-XXXX-XXXX-XXXX
    RHSA-2024:1234      → VND:RHSA-2024:1234
    DSA-1234-1          → DST:DSA-1234-1
    custom-id           → UNK:CUSTOM-ID
    

Scope Prefixes

ScopePrefix
EcosystemECO:
VendorVND:
DistributionDST:
UnknownUNK:

Usage

Canonicalizing an Identifier

var canonicalizer = new VexAdvisoryKeyCanonicalizer();

// Simple canonicalization
var result = canonicalizer.Canonicalize("CVE-2024-1234");
// result.AdvisoryKey = "CVE-2024-1234"
// result.Scope = VexAdvisoryScope.Global

// With aliases
var result = canonicalizer.Canonicalize(
    "GHSA-xxxx-xxxx-xxxx",
    aliases: new[] { "CVE-2024-1234" });
// result.AdvisoryKey = "ECO:GHSA-XXXX-XXXX-XXXX"
// result.Links contains both identifiers

Extracting CVE from Aliases

var cve = canonicalizer.ExtractCveFromAliases(
    new[] { "GHSA-xxxx-xxxx-xxxx", "CVE-2024-1234" });
// cve = "CVE-2024-1234"

JSON Serialization

{
  "advisory_key": "CVE-2024-1234",
  "scope": "global",
  "links": [
    {
      "identifier": "CVE-2024-1234",
      "type": "cve",
      "is_original": true
    },
    {
      "identifier": "GHSA-xxxx-xxxx-xxxx",
      "type": "ghsa",
      "is_original": false
    }
  ]
}

Determinism Guarantees

  1. Case normalization: All identifiers are normalized to uppercase internally
  2. Stable ordering: Links are ordered by original first, then alphabetically
  3. Deduplication: Duplicate aliases are removed during canonicalization
  4. Idempotence: Canonicalizing the same input always produces the same output

Unblocks

This contract unblocks the following tasks: