Files
git.stella-ops.org/docs/modules/authority/verdict-manifest.md
StellaOps Bot 634233dfed feat: Implement distro-native version comparison for RPM, Debian, and Alpine packages
- Add RpmVersionComparer for RPM version comparison with epoch, version, and release handling.
- Introduce DebianVersion for parsing Debian EVR (Epoch:Version-Release) strings.
- Create ApkVersion for parsing Alpine APK version strings with suffix support.
- Define IVersionComparator interface for version comparison with proof-line generation.
- Implement VersionComparisonResult struct to encapsulate comparison results and proof lines.
- Add tests for Debian and RPM version comparers to ensure correct functionality and edge case handling.
- Create project files for the version comparison library and its tests.
2025-12-22 09:50:12 +02:00

463 lines
11 KiB
Markdown

# Verdict Manifest Specification
> **Status**: Draft (Sprint 7100)
> **Last Updated**: 2025-12-22
> **Source Advisory**: `docs/product-advisories/archived/22-Dec-2026 - Building a Trust Lattice for VEX Sources.md`
## 1. Overview
A Verdict Manifest is a signed, immutable record of a VEX decisioning outcome. It captures all inputs used to produce a verdict, enabling deterministic replay and audit compliance.
### Purpose
1. **Auditability**: Prove exactly how a verdict was reached
2. **Reproducibility**: Replay the decision with identical results
3. **Compliance**: Meet regulatory requirements for security decisions
4. **Debugging**: Investigate unexpected verdict changes
---
## 2. Manifest Schema
### 2.1 Complete Schema
```typescript
interface VerdictManifest {
// Identity
manifestId: string; // Unique identifier
tenant: string; // Tenant scope
// Scope
assetDigest: string; // SHA256 of asset/SBOM
vulnerabilityId: string; // CVE/GHSA/vendor ID
// Inputs (pinned for replay)
inputs: VerdictInputs;
// Result
result: VerdictResult;
// Policy context
policyHash: string; // SHA256 of policy file
latticeVersion: string; // Trust lattice version
// Metadata
evaluatedAt: string; // ISO 8601 UTC timestamp
manifestDigest: string; // SHA256 of canonical manifest
}
interface VerdictInputs {
sbomDigests: string[]; // SBOM document digests
vulnFeedSnapshotIds: string[]; // Feed snapshot identifiers
vexDocumentDigests: string[]; // VEX document digests
reachabilityGraphIds: string[]; // Call graph identifiers
clockCutoff: string; // Evaluation timestamp
}
interface VerdictResult {
status: "affected" | "not_affected" | "fixed" | "under_investigation";
confidence: number; // 0.0 to 1.0
explanations: VerdictExplanation[];
evidenceRefs: string[]; // Attestation/proof references
}
interface VerdictExplanation {
sourceId: string;
reason: string;
provenanceScore: number;
coverageScore: number;
replayabilityScore: number;
strengthMultiplier: number;
freshnessMultiplier: number;
claimScore: number;
}
```
### 2.2 Identity Fields
| Field | Type | Description |
|-------|------|-------------|
| `manifestId` | string | Format: `verd:{tenant}:{asset_short}:{vuln_id}:{timestamp}` |
| `tenant` | string | Tenant identifier for multi-tenancy |
### 2.3 Input Pinning
All inputs that affect the verdict must be pinned for deterministic replay:
| Field | Description |
|-------|-------------|
| `sbomDigests` | SHA256 digests of SBOM documents used |
| `vulnFeedSnapshotIds` | Identifiers for vulnerability feed snapshots |
| `vexDocumentDigests` | SHA256 digests of VEX documents considered |
| `reachabilityGraphIds` | Identifiers for call graph snapshots |
| `clockCutoff` | Timestamp used for freshness calculations |
### 2.4 Verdict Result
The result section contains the actual verdict and full explanation:
| Field | Description |
|-------|-------------|
| `status` | Final verdict: affected, not_affected, fixed, under_investigation |
| `confidence` | Numeric confidence score (0.0 to 1.0) |
| `explanations` | Per-source breakdown of scoring |
| `evidenceRefs` | Links to attestations and proof bundles |
---
## 3. Deterministic Serialization
### 3.1 Canonical JSON
Manifests are serialized using canonical JSON rules:
1. Keys sorted alphabetically (ASCII order)
2. No insignificant whitespace
3. UTF-8 encoding without BOM
4. Timestamps in ISO 8601 format with 'Z' suffix
5. Arrays sorted by natural key (sourceId, then score)
6. Numbers without trailing zeros
### 3.2 Digest Computation
The manifest digest is computed over the canonical JSON:
```csharp
public static string ComputeDigest(VerdictManifest manifest)
{
var json = CanonicalJsonSerializer.Serialize(manifest with { ManifestDigest = "" });
var bytes = Encoding.UTF8.GetBytes(json);
var hash = SHA256.HashData(bytes);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
```
---
## 4. Signing
### 4.1 DSSE Envelope
Verdict manifests are signed using [DSSE (Dead Simple Signing Envelope)](https://github.com/secure-systems-lab/dsse):
```json
{
"payloadType": "application/vnd.stellaops.verdict+json",
"payload": "<base64-encoded-manifest>",
"signatures": [
{
"keyid": "projects/stellaops/keys/verdict-signer-2025",
"sig": "<base64-encoded-signature>"
}
]
}
```
### 4.2 Predicate Type
For in-toto attestations:
```
https://stella-ops.org/attestations/vex-verdict/1
```
### 4.3 Rekor Integration
Optionally, verdicts can be logged to Sigstore Rekor for transparency:
```json
{
"rekorLogId": "rekor.sigstore.dev",
"rekorLogIndex": 12345678,
"rekorEntryUrl": "https://rekor.sigstore.dev/api/v1/log/entries/..."
}
```
---
## 5. Storage
### 5.1 PostgreSQL Schema
```sql
CREATE TABLE authority.verdict_manifests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
manifest_id TEXT NOT NULL UNIQUE,
tenant TEXT NOT NULL,
-- Scope
asset_digest TEXT NOT NULL,
vulnerability_id TEXT NOT NULL,
-- Inputs (JSONB)
inputs_json JSONB NOT NULL,
-- Result
status TEXT NOT NULL,
confidence DOUBLE PRECISION NOT NULL,
result_json JSONB NOT NULL,
-- Policy context
policy_hash TEXT NOT NULL,
lattice_version TEXT NOT NULL,
-- Metadata
evaluated_at TIMESTAMPTZ NOT NULL,
manifest_digest TEXT NOT NULL,
-- Signature
signature_json JSONB,
rekor_log_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### 5.2 Indexing Strategy
| Index | Purpose |
|-------|---------|
| `(tenant, asset_digest, vulnerability_id)` | Primary lookup |
| `(tenant, policy_hash, lattice_version)` | Policy version queries |
| `(evaluated_at)` BRIN | Time-based queries |
| Unique: `(tenant, asset_digest, vulnerability_id, policy_hash, lattice_version)` | Ensure one verdict per configuration |
---
## 6. Replay Verification
### 6.1 Verification Protocol
1. Retrieve stored manifest by ID
2. Fetch pinned inputs (SBOM, VEX docs, feeds) by digest
3. Re-execute trust lattice evaluation with identical inputs
4. Compare result with stored verdict
5. Verify signature if present
### 6.2 Verification Request
```http
POST /api/v1/authority/verdicts/{manifestId}/replay
Authorization: Bearer <token>
```
### 6.3 Verification Response
```json
{
"success": true,
"originalManifest": { ... },
"replayedManifest": { ... },
"differences": [],
"signatureValid": true,
"verifiedAt": "2025-12-22T15:30:00Z"
}
```
### 6.4 Failure Handling
When replay produces different results:
```json
{
"success": false,
"originalManifest": { ... },
"replayedManifest": { ... },
"differences": [
{
"field": "result.confidence",
"original": 0.82,
"replayed": 0.79,
"reason": "VEX document digest mismatch"
}
],
"signatureValid": true,
"error": "Verdict replay produced different confidence score"
}
```
---
## 7. API Reference
### Get Verdict Manifest
```http
GET /api/v1/authority/verdicts/{manifestId}
Authorization: Bearer <token>
Response: 200 OK
{
"manifest": { ... },
"signature": { ... },
"rekorEntry": { ... }
}
```
### List Verdicts by Scope
```http
GET /api/v1/authority/verdicts?assetDigest={digest}&vulnerabilityId={cve}
Authorization: Bearer <token>
Response: 200 OK
{
"verdicts": [ ... ],
"pageToken": "..."
}
```
### Replay Verdict
```http
POST /api/v1/authority/verdicts/{manifestId}/replay
Authorization: Bearer <token>
Response: 200 OK
{
"success": true,
...
}
```
### Download Signed Manifest
```http
GET /api/v1/authority/verdicts/{manifestId}/download
Authorization: Bearer <token>
Response: 200 OK
Content-Type: application/vnd.stellaops.verdict+json
Content-Disposition: attachment; filename="verdict-{manifestId}.json"
```
---
## 8. JSON Schema
### verdict-manifest.schema.json
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/verdict-manifest/1.0.0",
"type": "object",
"required": [
"manifestId",
"tenant",
"assetDigest",
"vulnerabilityId",
"inputs",
"result",
"policyHash",
"latticeVersion",
"evaluatedAt",
"manifestDigest"
],
"properties": {
"manifestId": {
"type": "string",
"pattern": "^verd:[a-z0-9-]+:[a-f0-9]+:[A-Z0-9-]+:[0-9]+$"
},
"tenant": { "type": "string", "minLength": 1 },
"assetDigest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"vulnerabilityId": { "type": "string", "minLength": 1 },
"inputs": { "$ref": "#/$defs/VerdictInputs" },
"result": { "$ref": "#/$defs/VerdictResult" },
"policyHash": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"latticeVersion": {
"type": "string",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
},
"evaluatedAt": {
"type": "string",
"format": "date-time"
},
"manifestDigest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
}
},
"$defs": {
"VerdictInputs": {
"type": "object",
"required": ["sbomDigests", "vulnFeedSnapshotIds", "vexDocumentDigests", "clockCutoff"],
"properties": {
"sbomDigests": {
"type": "array",
"items": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }
},
"vulnFeedSnapshotIds": {
"type": "array",
"items": { "type": "string" }
},
"vexDocumentDigests": {
"type": "array",
"items": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }
},
"reachabilityGraphIds": {
"type": "array",
"items": { "type": "string" }
},
"clockCutoff": {
"type": "string",
"format": "date-time"
}
}
},
"VerdictResult": {
"type": "object",
"required": ["status", "confidence", "explanations"],
"properties": {
"status": {
"type": "string",
"enum": ["affected", "not_affected", "fixed", "under_investigation"]
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"explanations": {
"type": "array",
"items": { "$ref": "#/$defs/VerdictExplanation" }
},
"evidenceRefs": {
"type": "array",
"items": { "type": "string" }
}
}
},
"VerdictExplanation": {
"type": "object",
"required": ["sourceId", "reason", "claimScore"],
"properties": {
"sourceId": { "type": "string" },
"reason": { "type": "string" },
"provenanceScore": { "type": "number", "minimum": 0, "maximum": 1 },
"coverageScore": { "type": "number", "minimum": 0, "maximum": 1 },
"replayabilityScore": { "type": "number", "minimum": 0, "maximum": 1 },
"strengthMultiplier": { "type": "number", "minimum": 0, "maximum": 1 },
"freshnessMultiplier": { "type": "number", "minimum": 0, "maximum": 1 },
"claimScore": { "type": "number", "minimum": 0, "maximum": 1 }
}
}
}
}
```
---
## Related Documentation
- [Trust Lattice Specification](../excititor/trust-lattice.md)
- [Authority Architecture](./architecture.md)
- [DSSE Signing](../../dev/dsse-signing.md)
- [API Reference](../../09_API_CLI_REFERENCE.md)