ADR-019: VEX signature verification as a mandatory ingest gate

Formerly docs/architecture/ADR-EXCITITOR-VEX-SIGNATURE-VERIFICATION-INGEST-GATE.md.

FieldValue
StatusAccepted
Date2026-05-01
SprintSPRINT_20260501_020_Excititor_signature_verifier_interface (archived after acceptance)
Audit sourcemicroservice-audit-pass2-2026-04-29.md §E1
OwnersExcititor team, Project Manager
Supersedesn/a
RelatedSPRINT_20260501_021…023 (CSAF wiring, schema validation, AOC guard)

For: engineers on the Stella Ops Concelier/Excititor VEX pipeline and reviewers who need to understand why every VEX connector must verify a document’s signature before it reaches the raw sink. This ADR makes signature verification a hard ingest gate so that a tampered or unsigned VEX document cannot be normalized and weighted as if it were trusted.

Context

Audit pass 2 (advisory §E1) found that none of the six CSAF VEX connectors (RedHatCsafConnector, UbuntuCsafConnector, OracleCsafConnector, CiscoCsafConnector, MsrcCsafConnector, RancherHubConnector) called any signature verifier before storing raw documents, and the only connector that did invoke a verifier (OciOpenVexAttestationConnector) treated the result as advisory: it captured signatureMetadata but never rejected the document when verification returned null or failed. The default IVexSignatureVerifier binding was VerificationDisabledVexSignatureVerifier, a class that unconditionally returned null and was registered as the production fallback in StellaOps.Excititor.WebService.Program. The net effect was that a tampered or unsigned VEX document reached the raw sink, was normalized, and then weighted by VexLens as if signed.

Two verifier surfaces existed in parallel:

Decision

  1. Verification is a mandatory ingest gate. Connectors MUST produce raw documents through VexConnectorBase.CreateRawDocumentAsync(context, format, sourceUri, content, metadata, verificationOptions, ct), which performs verification before the digest is computed and the document is handed back. The synchronous CreateRawDocument(...) overload is [Obsolete(DiagnosticId="EXCITITOR-VRF-01")].

  2. StellaOps.Cryptography is the only verification primitive. All signature verification is delegated to ICryptoProviderRegistry. The existing ProductionVexSignatureVerifier is the implementation; no new crypto primitives are introduced. This honours the regional crypto plugin contract (FIPS / GOST / SM / eIDAS).

  3. The V1 surface becomes a fail-closed adapter. The legacy VerificationDisabledVexSignatureVerifier is deleted. IVexSignatureVerifier is satisfied exclusively by StellaOps.Excititor.Core.Verification.VexSignatureVerifierV1Adapter, which routes every call to V2 and throws VexSignatureRequiredException on missing/invalid signatures. The V1 contract returning null is reserved for advisory “unsigned, decide at the connector boundary” cases.

  4. AllowUnsigned is non-default and forbidden in production. Per-fetch VexConnectorVerificationOptions.AllowUnsigned == true admits unsigned documents and stamps vex.signature.status=unsigned metadata, but emits a Warning log entry and is rejected by a hosted startup validator (VexConnectorVerificationStartupValidator) when IHostEnvironment.IsProduction() is true and the global VexSignatureVerification:Connectors:AllowUnsigned knob is set.

  5. Stable metadata keys for the AOC guard. The verifier’s success metadata is merged into the document’s Metadata using the VexSignatureMetadataKeys constants (vex.signature.status, vex.signature.issuer, vex.signature.issuerId, vex.signature.keyId, vex.signature.method, vex.signature.profile, vex.signature.verifiedAt, vex.signature.transparency.entry, vex.signature.transparency.logId). Sprint 20260501-023 reads these keys in the AOC pre-normalize guard.

Consequences

Positive

Negative / mitigations

Out of scope (handed off)

Compliance