Global symbol source and catalog management
The Vulnerabilities hub owns symbol-source configuration and installation-wide pack availability. Console’s Symbol Sources and Symbol Marketplace pages use /api/vulnerabilities/v1/symbols/; a tenant selection does not partition this global catalog. Tenant-specific policy decisions remain outside the hub.
Access
Reads require symbols:read. Source changes, pack registration and availability changes require all three capabilities: symbols:read, symbols:write, and ops.admin. An ordinary tenant writer cannot change global source or pack state. A tenant header does not grant access or select a separate catalog.
What activation does
A catalog pack groups existing admitted hub manifests. Registration names their debug IDs and SHA-256 content identities; it does not upload a new package, download a remote archive, or create symbol results. Each member must already reference a hub blob-catalog entry.
Activation checks that the source is enabled and that every member’s object exists in the configured CAS with the expected SHA-256 digest and length. Hashing uses the Stella Ops cryptography library. A missing object, corrupt content, absent membership or disabled source prevents activation. The response reports the persisted availability state.
An inactive managed manifest does not resolve through the hub’s debug-ID endpoint. When a manifest belongs to several packs, any active pack from an enabled source makes it available. Deactivation removes that pack’s availability; it never deletes shared manifests or CAS bytes. Disabling a source also deactivates its packs. Reenabling the source does not reactivate them; each subsequent activation must check the content again. Existing manifests without catalog membership retain their direct lookup behavior.
Content integrity does not establish signature validity or publisher trust. The catalog labels supplied DSSE digests as unverified metadata. It does not offer a signed-only policy or report signature verification without an actual configured verifier. Cryptographic key custody and purpose lookup remain in the existing cryptography services; this API introduces no key format options, private keys, or signing fallback.
Sources and observation
Operators create sources with a stable source key, display name, type (vendor, distro, debuginfod, or mirror), optional HTTP(S) URL, priority, enabled state and freshness SLA. URL credentials are rejected. Source creation records no successful synchronization, signature result, or fabricated trust score. Without an observed successful sync, freshness is unavailable and age is absent. Freshness is calculated at read time from the observed success and SLA.
The registry does not itself fetch remote content. There is no sync-queue endpoint in this surface: the retiring host returned sync_queued without enqueueing a job. The retiring install/uninstall handlers also ignored tenant identity and only changed a global Boolean; they did not verify signatures or retrieve content. Those claims are not carried into the hub.
Routes
Paths below are relative to /api/vulnerabilities/v1/symbols.
| Method and path | Result |
|---|---|
GET /sources?includeDisabled=false | Source configuration and observed freshness; bounded to 1,000 sources |
GET /sources/summary | Enabled-source freshness counts |
GET /sources/{sourceKey} | Source detail |
POST /sources | Create configuration; duplicate keys return 409 |
PUT /sources/{sourceKey} | Update configuration; the body key must match the path |
DELETE /sources/{sourceKey} | Disable the source without deleting its history |
GET /marketplace?sourceKey=...&search=...&limit=50&offset=0 | Catalog search with total match count and stable pagination |
GET /marketplace/installed?limit=50&offset=0 | Globally active catalog selections |
GET /marketplace/{sourceKey}/{packId} | Pack metadata and activation eligibility |
POST /marketplace | Register membership of existing admitted manifests |
POST /marketplace/{sourceKey}/{packId}/activate | Verify CAS integrity, then activate availability |
POST /marketplace/{sourceKey}/{packId}/deactivate | Deactivate availability while preserving shared content |
Manifest upload, lookup and address resolution
The same owner exposes these routes under /api/vulnerabilities/v1/symbols:
| Method and path | Result |
|---|---|
POST /manifests | Admit a global document and its symbol content to CAS and metadata storage |
GET /manifests/{manifestId} | Read one available manifest by its sha256: identity |
GET /manifests | Query available manifest summaries by debug ID, code ID, binary name, platform, format, time or DSSE metadata presence |
POST /resolve | Resolve a bounded address batch from available manifests |
GET /status | Read available-manifest and available-symbol counts from the owner database |
Upload requires the same three global writer/admin capabilities as catalog registration. Other manifest operations require symbols:read. An upload body contains document plus optional base64 blob, mediaType and fileName. The document uses schemaVersion: "stellaops.symbols.manifest.v1", debugId, binaryName, lowercase format (elf, pe, macho, wasm), and a nonempty symbols collection. Optional fields are codeId, platform and sourceMappings. Each symbol names its address, size and mangled name; type, binding and source-location metadata are retained. The shared DTOs serialize unsigned 64-bit addresses, sizes and offsets as decimal strings, while accepting numeric input too. This preserves high virtual-address bits through JSON. Symbol names retain their exact Unicode text; the owner does not silently normalize them into another symbol name.
Without an explicit blob, the deterministic document bytes are themselves the stored symbol payload. With a blob, its actual bytes are stored instead. The CAS hash is always measured by the cryptography library. The manifest identity binds the document and its CAS metadata; replaying the same content preserves both the identity and original creation time. The response includes the actual blob hash, object key, media type and length. No payload enters PostgreSQL.
Ungrouped admitted manifests are immediately available. Registering one in a catalog pack opts it into pack availability control, so the pack must be activated before that manifest resolves again. All symbol reads share the symbols.available_manifest view. The closed HTTP client rechecks the owner; the retiring client’s disk cache cannot hide deactivation. Address matches use declared half-open extents, or the exact start address when size is unknown. The old 4 KiB extrapolation beyond a symbol’s extent is not reported as a match.
Legacy tenant-bearing documents are rejected rather than silently published globally. Prepare a versioned global document explicitly; old tenantId, caller-generated manifestId, hashAlgorithm and purported provenance fields are not upload inputs. Upload does not assert that a publisher signature was verified.
CLI migration
stella symbols upload --manifest <global-document.json> [--blob <symbol-content>] uses the CLI’s configured Vulnerabilities owner URL and normal API-token/Authority authentication. The old per-command --server and --tenant switches and unauthenticated fallback provider are removed. Configure StellaOps:VulnerabilitiesUrl / STELLAOPS_VULNERABILITIES_URL, or the existing shared backend URL, together with normal CLI credentials.
symbols healthreads the authenticated owner catalog status; unavailable responses fail.symbols validate --path <document.json>checks structure and explicitly performs no signature verification.symbols verify --path <envelope.json>requires a DSSE signature with exact payload typeapplication/vnd.stellaops.symbols.manifest.v1+json. Trust comes fromCrypto:SignatureTrust:JwksFiles:symbol-manifest. It refuses unsigned input, altered payloads, wrong keys, another purpose or payload type, and private/symmetric trust material. There is no raw-key or algorithm option and no implicit key generation.symbols ingestis withdrawn: its predecessor never extracted symbols and always failed. This rehome does not claim a new native-symbol extractor.
The producer-owned HTTP SDK is StellaOps.Vulnerabilities.Symbols.Client; its only project dependency is the closed hub Contracts SDK. It preserves upload, full manifest reads, debug-ID queries, address resolution and status calls with caller-provided authorization.
Catalog pages have a default limit of 50 and a maximum of 200. Search applies to the entire server catalog, not only the displayed page. Failed reads and actions remain visible in Console; they do not become an empty catalog or a successful action.
Storage and host wiring
The service uses its own Vulnerabilities PostgreSQL connection. Forward migration 009_symbol_management.sql adds observation fields to symbols.source and the symbols.catalog_manifest membership relation. Migration 010 adds the content lookup index and the shared availability view. They carry no retiring persistence code, demo rows, tenant columns, binary payloads, or golden-set tables.
The web host registers AddVulnerabilitiesSymbolManagementApi(), AddVulnerabilitiesSymbolBlobStorage(configuration), and MapVulnerabilitiesSymbolManagement() plus MapVulnerabilitiesSymbolManifests(). It eagerly resolves IContentAddressedBlobStore so missing storage configuration fails startup. Configure the existing neutral CAS provider under Vulnerabilities:SymbolBlobStorage; a local root can also be supplied through STELLAOPS_VULNERABILITIES_SYMBOL_BLOB_ROOT. This must be the durable hub CAS root containing the vuln/symbols/ objects. Self-hosted S3-compatible configuration uses the same neutral provider contract. Manifest upload requires write access to this symbol CAS; the separate retained-corpus publication mount remains read-only on the web host. No payload is stored in PostgreSQL.
Reverification
Source baseline: e7f96bc58f, with the BIN-8 candidate changes. This documents the candidate behavior; it is not a deployment receipt.
- Backend: run
SymbolManagementApiTestsandSymbolManifestApiTestsinStellaOps.Vulnerabilities.WebService.Testswithtools/scripts/test-targeted-xunit.ps1after building its changed producers. - Frontend: run
npx vitest run --config vitest.codex.config.ts src/app/features/security-risk/symbol-sources/symbol-management.spec.tsfromsrc/Web/StellaOps.Web, then the production Angular build. - Runtime retirement remains gated by the separate scratch-stack and operator deployment checks.
