Stella Product CSAF Advisory Contract v1

Contract: stella.ops/productCsafAdvisory@v1
Status: Active local contract
Sprint: SPRINT_20260430_003
Owner: Notify platform / Stella manufacturer product security
Runtime library: src/Notify/__Libraries/StellaOps.Notify.Connectors.Csaf/
Deterministic fixture location: docs/samples/product-advisories/STELLA-2026-0001.product-advisory.json with focused tests in src/Notify/__Tests/StellaOps.Notify.Connectors.Csaf.Tests/.
Replay/offline expectations: Export, local official CSAF JSON Schema validation, DSSE signing, feed drop, and offline verification run without network access.


Purpose

Defines Stella’s internal source model for product advisories about Stella Ops itself and the deterministic projection to outbound CSAF 2.0. The public feed URL is declared by /.well-known/stella-product-security.json as csafFeedUrl (see Stella Product Security Metadata v1); Router owns live public routing, while Notify owns the local advisory projection, DSSE envelope, delivery-ledger metadata, and operator filesystem feed drop.

Audience: Notify and Signer implementers, and reviewers of Stella’s outbound product-advisory pipeline.

This contract covers Stella-as-manufacturer advisories. It is not a customer/operator vulnerability-reporting format.

Version And Schema Pinning

FieldValue
Internal source modelstella.ops/productCsafAdvisory@v1
Feed index modelstella.ops/productCsafFeedIndex@v1
CSAF output version2.0
Signer payload idproduct-csaf-advisory-v1
DSSE payload typeapplication/vnd.stellaops.product.csaf-advisory.v1+json
CSAF document media typeapplication/vnd.csaf+json
DSSE envelope media typeapplication/vnd.dsse.envelope+json
Required signer profilestella-manufacturer-release
Local validation rule setstella-product-csaf-local-validation-v1
Official schema packageoasis-csaf-2.0 at docs/contracts/schemas/eu/csaf/2.0/

These constants are defined in ProductCsafAdvisoryConstants (AdvisorySchemaVersion, FeedIndexSchemaVersion, CsafVersion, PayloadTypeId, DssePayloadType, CsafMediaType, DsseEnvelopeMediaType, ExpectedSignerProfile, LocalValidationRuleSetVersion, OfficialCsafSchemaPackageId).

Schema governance follows the same release-pinning rule as docs/europe/decisions-log.md CRA-Q4 and DORA-Q2: pin by Stella minor release, do not auto-follow upstream schema changes, and graduate CSAF 2.1 only after explicit review. CSAF 2.1 remains a forward path, not the GA default.

Source Model

Required top-level fields (ProductCsafAdvisoryInput, validated by ProductCsafAdvisoryValidator):

FieldDescription
schemaVersionMust be stella.ops/productCsafAdvisory@v1 (exact match).
advisoryIdStella advisory id; must match STELLA-YYYY-NNNN (e.g. STELLA-2026-0001). Normalized to upper case.
title, summary, descriptionHuman-readable advisory content (non-empty).
languageMust be en (the only supported language; default en).
documentStatusDocument status string (default final); non-empty. Normalized to lower case.
documentVersionDocument/revision number string (default 1); non-empty.
tlpLabelMust be WHITE (the only supported TLP label). Normalized to upper case.
initialReleaseDate, currentReleaseDate, generatedAtUTC timestamps (a default/0001-01-01 value is rejected). Current release cannot precede initial release.
selfUrlHTTPS advisory URL.
publisherStella publisher block: category vendor, non-empty name (default Stella Ops), and HTTPS namespace (default https://stella-ops.org/security/advisories).
products[]At least one product, each with productId/name/version plus optional PURL/CPE.
vulnerabilities[]At least one entry, each with CVE id (CVE-YYYY-NNNN), title, description, severity, product status, optional CVSS, remediations, references.
evidenceRefs[], attestationRefs[]At least one immutable evidence reference and at least one signed attestation reference.

An optional channel (Notify NotifyChannel) may be supplied; when present its type must be custom.

The local validator (ProductCsafAdvisoryValidator.Validate) fails closed on: missing required fields; schemaVersion/language/tlpLabel/publisher.category not equal to their fixed values; advisoryId not matching STELLA-YYYY-NNNN; CVE ids not matching CVE-YYYY-NNNN; severity values outside LOW|MEDIUM|HIGH|CRITICAL; duplicate product ids or duplicate CVE ids; default/invalid dates and currentReleaseDate earlier than initialReleaseDate; non-HTTPS selfUrl or publisher.namespace; a vulnerability whose product status lists neither known-affected nor fixed products; vulnerability or remediation references to product ids absent from products[]; empty evidenceRefs/attestationRefs; channel.type other than custom; and malformed sha256:<64 lowercase hex> evidence hashes (only evidenceRefs entries that begin with sha256: are hash-checked). Per-vulnerability CVSS scoring shape is enforced later by the official CSAF/FIRST-CVSS schema validation, not by this local validator.

CSAF Projection

ProductCsafAdvisoryExporter.Export emits canonical JSON with:

The raw document is built and then canonicalized via CanonJson.CanonicalizeParsedJson; the artifact sha256 is computed over the canonical bytes (CanonJson.Sha256Prefixed). The exporter runs local validation (ValidateDocument) and then official-schema validation, and throws ProductCsafAdvisoryValidationException if either status is not passed.

Output bytes are validated against the vendored OASIS CSAF 2.0 JSON Schema and local FIRST CVSS scoring schemas before hashing and signing. The official validator is fail-closed: a missing schema package or incomplete/hash-mismatched package returns status blocked; parse failures, unresolved local schema references, or schema-evaluation errors return status failed. Either non-passed status aborts publication with a deterministic issue list.

Production signing calls SignerPipeline.SignAsync with a SigningRequest.CreateDirectPayload(...) request (ProductCsafSignerPipelineAdvisorySigner) so the canonical CSAF JSON payload remains the DSSE payload while proof-of-entitlement, release-integrity verification, quota, provider resolution, and audit stay on the normal Signer path. The returned envelope is re-checked (payloadType, byte-equal payload, non-empty signatures/key ids) before use.

ProductCsafFeedExportService.ExportSignedAsync records the result in a Notify delivery-ledger entry (NotifyDelivery, status Queued, tenant defaulting to stella-product, channel product-csaf-feed). Its Metadata dictionary always carries:

and conditionally carries csaf.cryptoProfile (when Signer returns an active crypto profile), csaf.signer.fingerprint (when the signer supplies one; the SignerPipeline signer leaves it null), and csaf.signer.auditId (when an audit id is available). A companion ProductCsafDeliveryAuditEvent (notify.product_csaf.advisory.prepared) records the advisory id, channel id, document and DSSE hashes, and signer key id/fingerprint.

DSSE Verification

The DSSE envelope (ProductCsafAdvisoryDsseService) wraps the canonical CSAF payload directly: payloadType is the product CSAF payload type, payload is the base64url-encoded canonical CSAF bytes, and signatures[] carry keyid/sig. The PAE is computed with DssePreAuthenticationEncoding.Compute. Offline verification (ProductCsafOfflineVerifier) delegates to Signer Core EuArtifactOfflineVerifier with:

Tampering with the CSAF payload after signing must fail closed with deterministic verifier issues such as canonical_payload_hash_mismatch and no_valid_signature (both literal strings emitted by EuArtifactOfflineVerifier).

Operators can run the same offline check from the CLI via the product-csaf-advisory verify command (ProductCsafAdvisoryVerifyCommandGroup): it takes the canonical CSAF file, a --envelope DSSE file, --trust-root PEM file(s)/director(ies), optional --key-id/--trust-root-id, and --output table|json. It refuses remote (http/https) inputs, recomputes the canonical payload hash locally with CanonJson, and exits non-zero (CliExitCodes.VerificationFailed) on any verifier issue.

Signer must pin the primary DSSE algorithm from its active crypto profile before provider resolution. Built-in pins are fips -> ES256, eidas -> ES256, gost -> GOST12-256, sm -> SM2, and pq -> DILITHIUM3; deployment-specific ProfileAlgorithmPins may override these only by explicit Signer configuration.

Official CSAF Schema Status

Official JSON Schema validation is active and local-only. The approved package is:

ProductCsafOfficialSchemaValidator verifies each asset’s pinned SHA-256 hash and its exact byte length before validation, and disables runtime schema fetching (the JsonSchema.Net SchemaRegistry.Fetch hook returns null). Each schema is registered under its canonical OASIS/FIRST URI; the FIRST CVSS schemas are also registered under alias URIs (e.g. https://www.first.org/cvss/cvss-v3.1.json) so CSAF $refs resolve locally. The schema root is resolved from AppContext.BaseDirectory/Schemas/Csaf/2.0 or by walking parent directories for docs/contracts/schemas/eu/csaf/2.0. FIRST CVSS v2.0/v3.0/v3.1 schemas are loaded as draft-07 (with the $schema line rewritten from draft-04 to draft-07 in memory only for JsonSchema.Net compatibility); the three OASIS CSAF schemas are loaded as draft 2020-12. The vendored bytes remain hash-pinned.

Filesystem Feed Drop

ProductCsafFeedDropWriter/ProductCsafFeedChannel write the signed advisory to an operator-supplied output directory. ProductCsafFeedChannel.PublishAsync exports, signs, runs offline verification, and only then writes the feed (publication aborts with offline_verification:<issue> errors if verification fails). The drop layout is:

The feed index (stella.ops/productCsafFeedIndex@v1, canonicalized via CanonJson) carries schemaVersion, csafVersion, generatedAt, and an items[] array; each item records advisoryId, csafFile, csafSha256, dsseFile, dsseSha256, payloadTypeId (product-csaf-advisory-v1), and schemaPinId (2.0). The public feed URL is surfaced by the Gateway product-metadata endpoint (ProductMetadataDocumentProvider) as csafFeedUrl = <baseUrl>/security/csaf/feed.json.