CVE-to-Symbol Mapping
Last updated: 2026-09-28 (Concelier-era sources replaced by the Vulnerabilities hub path, PCD-163). Owner: Scanner Guild with the Vulnerabilities hub.
Audience: Scanner and Vulnerabilities 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.
History. Until 2026-09-11 the upstream symbols came from Concelier’s
AffectedSymbolprojection, with Concelier offline bundles (schema v1/v2) and a curated Top-N overlay for air-gapped sites. Concelier is frozen undersrc/__Obsoleted/Concelier/, and those providers, exporters and formats went with it. The pre-2026-09-28 text of this guide, including those formats, is kept atdocs-archive/modules/graph/reachability/guides/cve-symbol-mapping.md.
1. Overview
To determine whether a vulnerability is reachable, Stella Ops resolves:
- CVE identifiers (e.g.,
CVE-2024-1234) - Package coordinates (e.g.,
pkg:npm/lodash@4.17.21) - Affected symbols (e.g.,
lodash.template,openssl:EVP_PKEY_decrypt)
The mapping is used by SliceExtractor to target the right symbols and by downstream VEX decisions.
Scanner resolves sinks through one ICveSymbolMappingService, a decorator chain whose order is the precedence ladder: explicit store rows (PostgresCveSymbolMappingRepository) win over upstream advisory symbols, which win over patch-diff surfaces. Each bridge joins the chain only when the host registers its source service (src/Scanner/__Libraries/StellaOps.Scanner.Reachability/ServiceCollectionExtensions.cs:46-83).
2. Data Sources
2.1 Patch Diff Surfaces
Highest-fidelity source: compute method-level diffs between vulnerable and fixed versions.
Implementation: StellaOps.Scanner.VulnSurfaces. VulnSurfaceCveSymbolMappingService joins the chain when the host registers the surface read service IVulnSurfaceService (ServiceCollectionExtensions.cs:72-79). Scanner.Worker registers it only when VulnSurfaces:FeedTrigger:Enabled is set (VulnSurfacesServiceCollectionExtensions.cs:94-128, Scanner.Worker/Program.cs:256). The request source is the Vulnerabilities hub’s vuln.facts stream: VulnSurfaceHubFeedConsumer (Scanner.Worker/Processing/VulnSurfaces/) reads it over the hub’s owner API, keeps its checkpoint in Scanner’s own database, and submits one computation per OSV affected fact that declares a fix and starts at a concrete version, in a configured ecosystem (nuget, maven, npm, pypi, gem, composer). The same flag also requires Scanner:Worker:Reachability:Enabled with an absolute HubAdvisoryBaseUrl, because the consumer uses the reachability lane’s hub client. Surfaces are keyed by the purl namespace and name and the range’s introduced version, and the scan-time lookup matches that version exactly.
2.2 Upstream Advisory Symbols (Vulnerabilities hub)
The hub serves upstream symbols at GET /api/vulnerabilities/v1/advisories/{vulnerabilityId}/symbols (src/Vulnerabilities/StellaOps.Vulnerabilities.WebService/Program.cs:451, policy VulnerabilitiesPolicies.AdvisoryRead, scope advisory:read). It decodes the OSV documents behind live facts with OsvAffectedSymbolReader (src/Vulnerabilities/__Libraries/StellaOps.Vulnerabilities.Contracts/Corpus/OsvAffectedSymbolReader.cs:14) and answers AdvisorySymbolsResponse (.../Corpus/AdvisorySymbolsResponse.cs:6).
Scanner.Worker reads it through HubHttpAffectedSymbolProvider, registered as IAdvisoryAffectedSymbolProvider (src/Scanner/StellaOps.Scanner.Worker/Program.cs:844-845) against Scanner:Worker:Reachability:HubAdvisoryBaseUrl. Its presence adds AffectedSymbolCveSymbolMappingService to the chain (ServiceCollectionExtensions.cs:62-70). Bounds, errors and tests: advisory affected symbols.
2.3 Offline: the Compact Vulnerability Database
For air-gapped environments, stella vuln-db export writes a compact offline database that carries the upstream symbols as reachability sinks (ReachabilitySinkProjector over OsvAffectedSymbolReader, src/Cli/StellaOps.Cli/Commands/VulnDb/CompactVulnDbExporter.cs:230). stella sbom check reads it through CompactDbCveSymbolMappingService (src/Cli/StellaOps.Cli/Commands/Sbom/ReachabilityAssessment.cs:53), so offline verdicts use the same mapping contract as the server.
Legal boundary: upstream advisory symbols are the only symbols in the compact database. 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 -> Upstream Symbols (hub)
The live contract is the hub’s AdvisorySymbolsResponse (src/Vulnerabilities/__Libraries/StellaOps.Vulnerabilities.Contracts/Corpus/AdvisorySymbolsResponse.cs:6), consumed in Scanner through IAdvisoryAffectedSymbolProvider (src/Scanner/__Libraries/StellaOps.Scanner.Reachability/Services/AdvisoryAffectedSymbolContracts.cs:10).
3.2 CVE + PURL -> Affected Symbols
src/Scanner/__Libraries/StellaOps.Scanner.VulnSurfaces/Services/IVulnSurfaceService.cs:
public interface IVulnSurfaceService
{
Task<VulnSurfaceResult> GetAffectedSymbolsAsync(
string cveId,
string purl,
CancellationToken cancellationToken = default);
Task<bool> HasSurfaceAsync(
string cveId,
CancellationToken cancellationToken = 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. Availability
An unavailable hub leaves upstream symbols unavailable, and reachability stays unknown for those CVEs; it does not fall back to a stale cache (advisory affected symbols). The compact database is a file the operator refreshes by re-export.
5. Offline and Export Formats
5.1 Compact vulnerability database
The current offline carrier for upstream symbols (§2.3). Export: stella vuln-db export.
5.2 Concelier offline bundle schema v1/v2 (withdrawn)
Withdrawn with Concelier; no live code reads or writes it. The format description is kept in the archived guide §5.1-§5.2.
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:
- Approved ecosystems only:
pypi,gem/RubyGems,nuget, andcomposer/Packagist can export.npmandmavenare always excluded with auditable reason codes until written permission or a commercial arrangement is recorded. - No package content: rows carry canonical machine-generated signatures and hashes only. Package archives, source files, object/decompiled code, method bodies, comments, docstrings, README/example text, literal expressions, default values, annotation strings, and copied declarations are forbidden and fail the policy gate.
- Required provenance: every row carries ecosystem, registry, exact download endpoint, purl, vulnerable/fixed versions, CVE/advisory id, package license expression/classification, ToS snapshot id/date, registry client controls, audit-log references, and the deterministic surface computation digest.
- License gate: unknown, proprietary, custom, restricted, non-commercial, no-derivatives, and no-reverse-engineering package-license classifications fail closed. OSS/free/permissive classifications may proceed when the other gates pass.
- Registry controls: official API use, User-Agent/contact, rate-limit/Retry-After handling, caching/dedupe discipline, no bulk mirroring, and per-registry audit logs are required. Packagist rows also record the actual code download host.
- Determinism and signing: canonical bundle bytes omit timestamps, sort rows/sinks/references ordinally, and produce stable sha256 digests over two fresh exports. Signed exports add row-level DSSE envelopes over the canonical row payload and verification fails closed if a row is changed after signing.
- Loader and precedence: the disconnected loader fail-closes the whole bundle on any unsigned, non-canonical, tampered, untrusted, no-audit, no-provenance, or policy-rejected row. Loaded rows stay read-only and feed
VulnSurfaceCveSymbolMappingService, preserving the existing ladder: curated > upstream advisory symbols > derived patch-diff surfaces > package-symbols > heuristic. No approved-subset import writes package-derived rows intocve_symbol_mappings. - Third-party data notice source: derived-surface offline kits must state that the artifact contains machine-generated security-analysis facts derived from third-party package versions, not package archives or source content; ecosystem-specific registry terms and package-license classifications control whether a row is included.
NOTICE OBLIGATION ACTIONED (OK-11, 2026-08-25). Counsel’s three §5 blocks — the derived-surface statement, BUSL/EULA
"Third-Party Data"carve-out, and non-affiliation sentence — now appear verbatim in the customer-reachingthird-party-licenses/DATA-NOTICES.md.tools/scripts/validate/check_patch_diff_data_notice.pyextracts those blocks from the canonical counsel reply and fails if the root notice, release-bundle copy, offline-kit staging call, release-bundle staging call, manifest checksum surface, or either wholesale runtime-assets copy drifts. Its--self-testremoves each carrier/gate link in turn and proves the detector goes red. The offline-kit builder and release-bundle stager also validate their staged notice before producing a customer artifact. No packaging pipeline emits a derived-surface bundle yet; these checks are the mandatory first-ship precondition, not evidence of a live distribution.
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:
- Redistribution licensing, not copyright. The derived surface artifact itself (method identifiers, normalized signatures, hashes, CVE/package/version provenance) is treated as uncopyrightable security-analysis facts — that part is settled in counsel’s favour (
docs/legal/decisions/patch-diff-surface-airgap-export-counsel-thread-reply.md§1). The gating risk is contractual: the registry Terms of Service govern whether Stella may redistribute surfaces derived from packages downloaded through that registry.- npm:
registry.npmjs.orgToS carries a specific security-data clause — “data about the security of Packages” (vulnerability reports, audit-status, supplementary security documentation) may be used only for personal/internal-business purposes and not provided to others or used as part of products/services. CVE-linked patch-diff surfaces are plausibly “data about the security of Packages,” so redistributing them in a kit would require written npm/GitHub confirmation that Stella-computed surfaces are out of scope of that clause (counsel reply §2–3 npm row, §6 “Keep blocked”). - Maven Central: Sonatype’s current Central terms + FAQ treat commercial/scanner-like, automated, high-volume use (explicitly “use by a commercial service, platform, tool, scanner, hosted build, cache, mirror, or similar”) as restricted, and there is no affirmative permission to redistribute derived security metadata generated from Central downloads. Export would require a Sonatype written permission or a commercial arrangement (counsel reply §2–3 Maven row, §6 “Keep blocked”).
- npm:
- Operator decision (2026-06-14): ABANDONED, not pursued. Obtaining the npm/GitHub written confirmation and the Sonatype commercial arrangement is the only path counsel left open for these two ecosystems. The operator has decided not to pursue that registry permission. npm and Maven Central are therefore declared explicitly out of scope for offline-kit surface export. They are not a pending to-do; they are an intentional, permanent exclusion. (Internal-only use of computed npm/Maven surfaces — the mapping bridge and scan-time resolution — is unaffected; only exporting derived surfaces into redistributable offline kits is excluded.)
- Fail-closed by construction. The exporter does not merely omit these ecosystems — it actively rejects them with auditable reason codes so a future caller cannot silently re-enable them:
- Producer gate:
VulnSurfaceExportPolicy.Evaluaterejectsnpm→ecosystem.blocked.npm_written_permission_requiredandmaven→ecosystem.blocked.maven_central_permission_required; any ecosystem outside the approved set{composer, gem, nuget, pypi}→ecosystem.not_approved(src/Scanner/__Libraries/StellaOps.Scanner.VulnSurfaces/Export/VulnSurfaceExportPolicy.cs:95-127). The OSV registry spellingsRubyGems/Packagistfold togem/composerfirst (Feed/VulnSurfaceEcosystems.cs:30-40), so the approved set is precisely the four counsel-approved registries. - Loader gate: the disconnected bundle loader fail-closes npm/Maven Central rows even when a trusted DSSE signature verifies — a signed-but-blocked row cannot smuggle a surface in (
VulnSurfaceExportLoaderTests.LoadSignedBundle_BlockedEcosystems_FailClosedEvenWhenTrustedSignatureVerifies,src/Scanner/__Libraries/StellaOps.Scanner.VulnSurfaces.Tests/VulnSurfaceExportLoaderTests.cs:139-142). - Pinned by tests: producer exclusion
VulnSurfaceExportPolicyTests.BuildCanonical_ExportsOnlyCounselApprovedEcosystems_AndExcludesNpmAndMaven; both the producer (13 facts) and loader (10 facts) suites are green.
- Producer gate:
- Unknown / proprietary / custom / restricted package licenses are also fail-closed for the supported four (
license.blocked.*), so the kit only ever ships surfaces over OSS/free/permissive packages from the four approved registries.
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 (withdrawn)
The curated overlay (CuratedAffectedSymbolProvider, LadderAffectedSymbolProvider) lived in StellaOps.Concelier.Core/Signals and was withdrawn with Concelier. The curated rung of the ladder is now the explicit store (PostgresCveSymbolMappingRepository, §1). The format is kept in the archived guide §5.4.
6. Fallback Behavior
When no surface or advisory mapping is available, the surface service returns an empty symbol list with low confidence and Source = "heuristic". Callers may inject an IPackageSymbolProvider (src/Scanner/__Libraries/StellaOps.Scanner.VulnSurfaces/Services/IPackageSymbolProvider.cs) to supply public-symbol fallbacks.
7. Related Documentation
Created: 2025-12-22. See Sprint 3810 for implementation details.
