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.
This commit is contained in:
462
docs/modules/authority/verdict-manifest.md
Normal file
462
docs/modules/authority/verdict-manifest.md
Normal file
@@ -0,0 +1,462 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user