# CVE-to-Symbol Mapping

Last updated: 2026-06-13 (derived patch-diff surface approved-subset export policy/format, sprint SPRINT_20260612_013). Owner: Scanner Guild + Concelier Guild.

Audience: Scanner and Concelier contributors implementing or consuming CVE-to-symbol resolution.

This guide describes how Stella Ops maps CVE identifiers to the specific binary symbols and functions that drive reachability slices — the foundation for deciding whether a vulnerable function is actually called.


1. Overview

To determine whether a vulnerability is reachable, Stella Ops resolves:

The mapping is used by SliceExtractor to target the right symbols and by downstream VEX decisions.


2. Data Sources

2.1 Patch Diff Surfaces (Preferred)

Highest-fidelity source: compute method-level diffs between vulnerable and fixed versions.

Implementation: StellaOps.Scanner.VulnSurfaces

2.2 Advisory Linksets (Concelier)

Scanner queries Concelier’s LNM linksets for package coordinates and optional symbol hints.

Implementation: StellaOps.Scanner.Advisory -> Concelier /v1/lnm/linksets/{cveId} or /v1/lnm/linksets/search

2.3 Offline Bundles

For air-gapped environments, precomputed bundles map CVEs to packages and symbols.

Implementation:

Legal boundary: upstream advisory symbols (the AffectedSymbol projection) enter schema v2 bundles. Stella-DERIVED patch-diff surfaces (vuln_surfaces) use a separate approved-subset export policy/format only for PyPI, RubyGems, NuGet, and Packagist. npm and Maven Central derived surfaces are not supported for kit export by design — they are redistribution-licensing excluded (npm security-data ToS clause; Sonatype Central commercial/scanner-use restriction) and the operator has decided not to pursue the registry permission that was their only path (see §5.3 “Unsupported ecosystems: npm + Maven Central”). Internal-only use of npm/Maven surfaces is unaffected (docs/legal/decisions/patch-diff-surface-airgap-export-counsel-thread.md, reply).


3. Service Contracts

3.1 CVE -> Package/Symbol Mapping

public interface IAdvisoryClient
{
    Task<AdvisorySymbolMapping?> GetCveSymbolsAsync(string cveId, CancellationToken ct = default);
}

public sealed record AdvisorySymbolMapping
{
    public required string CveId { get; init; }
    public ImmutableArray<AdvisoryPackageSymbols> Packages { get; init; }
    public required string Source { get; init; } // "concelier" | "bundle"
}

public sealed record AdvisoryPackageSymbols
{
    public required string Purl { get; init; }
    public ImmutableArray<string> Symbols { get; init; }
}

3.2 CVE + PURL -> Affected Symbols

public interface IVulnSurfaceService
{
    Task<VulnSurfaceResult> GetAffectedSymbolsAsync(
        string cveId,
        string purl,
        CancellationToken ct = default);
}

public sealed record VulnSurfaceResult
{
    public required string CveId { get; init; }
    public required string Purl { get; init; }
    public required ImmutableArray<AffectedSymbol> Symbols { get; init; }
    public required string Source { get; init; } // "surface" | "package-symbols" | "heuristic"
    public required double Confidence { get; init; }
}

public sealed record AffectedSymbol
{
    public required string SymbolId { get; init; }
    public string? MethodKey { get; init; }
    public string? DisplayName { get; init; }
    public string? ChangeType { get; init; }
    public double Confidence { get; init; }
}

4. Caching Strategy

DataTTLNotes
Advisory linksets1 hourIn-memory cache; configurable TTL
Offline bundlesProcess lifetimeLoaded once from file

5. Offline Bundle Format

5.1 Schema v1 (legacy)

{
  "items": [
    {
      "cveId": "CVE-2024-1234",
      "source": "bundle",
      "packages": [
        {
          "purl": "pkg:npm/lodash@4.17.21",
          "symbols": ["template", "templateSettings"]
        }
      ]
    }
  ]
}

v1 bundles remain readable everywhere: FileAdvisoryBundleStore reads them natively, and BundleAffectedSymbolProvider loads packages[].symbols as function-granular facts.

5.2 Schema v2 (current — sprint SPRINT_20260612_012 AG1)

Produced by AffectedSymbolOfflineBundleExporter from the per-tenant Concelier AffectedSymbol projection (upstream advisory symbols only). v2 is a strict superset of v1 — v1 readers ignore the added fields — and adds per-symbol granularity + provenance so the projection read path rebuilds with zero loss:

{
  "schemaVersion": 2,
  "tenant": "default",
  "items": [
    {
      "cveId": "CVE-2021-38561",
      "source": "bundle",
      "packages": [
        {
          "purl": "pkg:golang/golang.org/x/text",
          "symbols": ["golang.org/x/text/language::ParseAcceptLanguage"]
        }
      ],
      "symbols": [
        {
          "symbol": "ParseAcceptLanguage",
          "symbolType": "function",
          "purl": "pkg:golang/golang.org/x/text",
          "module": "golang.org/x/text/language",
          "versionRange": ">=0, <0.3.7",
          "observationId": "obs-go-text-1",
          "provenance": {
            "source": "osv",
            "vendor": "open-source-vulnerabilities",
            "observationHash": "sha256:...",
            "upstreamId": "GO-2021-0113",
            "upstreamUrl": "https://osv.dev/vulnerability/GO-2021-0113",
            "extractionShape": "ecosystem_specific.imports[].symbols"
          }
        }
      ]
    }
  ]
}

Notes:

5.3 Derived Patch-Diff Surface Export v1 (approved subset)

Produced by StellaOps.Scanner.VulnSurfaces.Export from computed VulnSurface rows only after the 2026-06-12 counsel gates pass. This is a separate artifact from schema v2 advisory-symbol bundles: it does not write derived rows into the curated/upstream symbol store. The approved-subset loader verifies canonical signed bytes, trusted row-level DSSE signatures, row payload digests, registry/provenance policy, and the approved ecosystem boundary before exposing accepted rows as a read-only IVulnSurfaceService for the existing mapping read path.

{
  "schemaVersion": "stellaops.vuln-surface-export/v1",
  "rows": [
    {
      "cveId": "CVE-2026-0004",
      "advisoryId": "GHSA-demo",
      "ecosystem": "pypi",
      "registry": "pypi.org",
      "exactDownloadEndpoint": "https://files.pythonhosted.org/packages/demo-1.0.0.tar.gz",
      "purl": "pkg:pypi/demo@1.0.0",
      "packageName": "demo",
      "vulnerableVersion": "1.0.0",
      "fixedVersion": "1.0.1",
      "packageLicenseExpression": "MIT",
      "licenseClassification": "oss",
      "tosSnapshotId": "tos-2026-06-12",
      "tosSnapshotDate": "2026-06-12",
      "userAgent": "StellaOps/1.0 security-surface-export contact=security@example.invalid",
      "contact": "security@example.invalid",
      "rateLimitPolicy": "official-api-backoff",
      "retryAfterPolicy": "honor-retry-after",
      "auditLogReferences": ["audit:surface:20260612"],
      "computationDigest": "sha256:...",
      "sinks": [
        {
          "methodKey": "demo::Parser.parse(value)",
          "declaringType": "Parser",
          "methodName": "parse",
          "canonicalSignature": "parse(value)",
          "changeType": "modified",
          "vulnHash": "sha256:...",
          "fixedHash": "sha256:...",
          "parameterCount": 1,
          "returnType": "string"
        }
      ]
    }
  ]
}

Policy and format rules:

Unsupported ecosystems: npm + Maven Central (not supported by design)

npm and Maven Central derived patch-diff surfaces are NOT exported into offline kits, and this is a permanent product decision — not a temporary block. The supported export set is exactly the four counsel-approved ecosystems above: PyPI, RubyGems, NuGet, Packagist. Everything else fail-closes.

Why the two are excluded:

If the operator ever reverses this decision and obtains the npm/GitHub confirmation and/or Sonatype arrangement, re-enabling an ecosystem is a single, auditable change: record the written permission under docs/legal/decisions/, then add the ecosystem to VulnSurfaceExportPolicy.ApprovedEcosystems and remove its dedicated block branch. Until that recorded permission exists, npm and Maven Central stay unsupported.

5.4 Curated Top-N Bundle v1 (sprint SPRINT_20260612_012 AG2)

The deterministic backstop tier for ecosystems with NO upstream advisory symbols: the ~200–500 KEV-weighted CVEs whose affected symbols a human curator has hand-verified. Schema id stellaops.curated-affected-symbols/v1 (a distinct, versioned string — never confused with the integer-versioned upstream v2 schema above). Produced by curation tooling; loaded by CuratedAffectedSymbolProvider (StellaOps.Concelier.Core/Signals) and laddered over an upstream provider by LadderAffectedSymbolProvider.

{
  "schema": "stellaops.curated-affected-symbols/v1",
  "tenant": "default",
  "items": [
    {
      "cveId": "CVE-2099-0001",
      "curator": {
        "curator": "stellaops-security-research",
        "method": "patch-review",
        "curatedAt": "2026-06-13",
        "evidenceUrl": "https://example.invalid/advisories/CVE-2099-0001"
      },
      "symbols": [
        {
          "symbol": "parse",
          "symbolType": "method",
          "className": "Parser",
          "module": "example.demo",
          "purl": "pkg:nuget/Example.Demo",
          "versionRange": ">=1.0.0, <1.2.0",
          "confidence": 0.97
        }
      ]
    }
  ]
}

Format and policy rules:


6. Fallback Behavior

When no surface or advisory mapping is available, the service returns an empty symbol list with low confidence and Source = "heuristic". Callers may inject an IPackageSymbolProvider to supply public-symbol fallbacks.



Created: 2025-12-22. See Sprint 3810 for implementation details.