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
| Field | Value |
|---|---|
| Internal source model | stella.ops/productCsafAdvisory@v1 |
| Feed index model | stella.ops/productCsafFeedIndex@v1 |
| CSAF output version | 2.0 |
| Signer payload id | product-csaf-advisory-v1 |
| DSSE payload type | application/vnd.stellaops.product.csaf-advisory.v1+json |
| CSAF document media type | application/vnd.csaf+json |
| DSSE envelope media type | application/vnd.dsse.envelope+json |
| Required signer profile | stella-manufacturer-release |
| Local validation rule set | stella-product-csaf-local-validation-v1 |
| Official schema package | oasis-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):
| Field | Description |
|---|---|
schemaVersion | Must be stella.ops/productCsafAdvisory@v1 (exact match). |
advisoryId | Stella advisory id; must match STELLA-YYYY-NNNN (e.g. STELLA-2026-0001). Normalized to upper case. |
title, summary, description | Human-readable advisory content (non-empty). |
language | Must be en (the only supported language; default en). |
documentStatus | Document status string (default final); non-empty. Normalized to lower case. |
documentVersion | Document/revision number string (default 1); non-empty. |
tlpLabel | Must be WHITE (the only supported TLP label). Normalized to upper case. |
initialReleaseDate, currentReleaseDate, generatedAt | UTC timestamps (a default/0001-01-01 value is rejected). Current release cannot precede initial release. |
selfUrl | HTTPS advisory URL. |
publisher | Stella 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:
document.csaf_version = "2.0"document.category = "csaf_security_advisory"document.distribution.tlp.labelfromtlpLabelanddocument.langfromlanguagedocument.notes[]carrying the summary and description, and adocument.references[]selfentry pointing atselfUrldocument.publisher(category/name/namespace)document.titledocument.tracking.id = advisoryId,document.tracking.status,document.tracking.version,document.tracking.initial_release_date,document.tracking.current_release_datedocument.tracking.generator.engine.name = StellaOps.Notify.Connectors.Csafanddocument.tracking.generator.engine.version = stella-product-csaf-local-validation-v1document.tracking.revision_history[]with the initial publication record (dated atcurrentReleaseDate, numbered withdocumentVersion)product_tree.full_product_names[](each withname,product_id, and aproduct_identification_helpercarrying anycpe/purl)vulnerabilities[]withcve,notes,product_status(fixed/known_affected),references(an injected CVE-record reference plus the supplied references),remediations,title, and an optionalscores[].cvss_v3block (version3.1) emitted only when bothcvssBaseScoreandcvssVectorare present
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:
csaf.advisoryId,csaf.csafVersioncsaf.document.sha256,csaf.dsse.sha256csaf.localValidation.status,csaf.localValidation.ruleSetVersioncsaf.officialSchemaValidation.status(must bepassed)csaf.payloadType(application/vnd.stellaops.product.csaf-advisory.v1+json) andcsaf.payloadTypeId(product-csaf-advisory-v1)csaf.signatureAlgorithm,csaf.signatureProfilecsaf.signer.keyIdHash(the SHA-256 of the signer key id; the raw key id is never stored)
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:
- expected payload id
product-csaf-advisory-v1 - expected schema pin
2.0 - expected canonical payload hash from the exported CSAF bytes
- trust-root mode
AirGap(EuTrustRootMode.AirGap) - local manufacturer trust roots (
EuVerifierTrustRoot) constrained to signer profilestella-manufacturer-release
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:
- OASIS CSAF 2.0 schemas:
csaf_json_schema.json,provider_json_schema.json,aggregator_json_schema.json - FIRST CVSS transitive schemas:
transitive/first-cvss/cvss-v2.0.json,transitive/first-cvss/cvss-v3.0.json,transitive/first-cvss/cvss-v3.1.json
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:
advisories/<advisoryId>.csaf.json— the canonical CSAF document (application/vnd.csaf+json)advisories/<advisoryId>.csaf.json.dsse.json— the DSSE envelope (application/vnd.dsse.envelope+json)feed.json— the feed index at the directory root
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.
Related Contracts
- Stella Product Security Metadata v1 — publishes the
csafFeedUrlthat points at this feed. - OASIS CSAF 2.0 schemas — the vendored, hash-pinned schema package this contract validates against.
