24 KiB
Proof and Evidence Chain Technical Reference
Source Advisories:
- 29-Nov-2025 - SBOM to VEX Proof Pipeline Blueprint
- 01-Dec-2025 - Turning SBOM Data Into Verifiable Proofs
- 01-Dec-2025 - Proof-Linked VEX User Interface
- 02-Dec-2025 - Converting SBOM Data into Proof Chains
- 06-Dec-2025 - How to Build a Verifiable SBOM→VEX Chain
- 08-Dec-2025 - Defining Stella Ops' Proof‑Linked Advantage
- 08-Dec-2025 - Designing UX for Signed Evidence Trails
- 03-Dec-2025 - Comparing Proof‑Linked VEX UX Across Tools
Last Updated: 2025-12-14
1. CORE IDENTIFIERS & DATA MODEL
1.1 Canonical IDs
ArtifactID = sha256:<digest>
SBOMEntryID = <sbomDigest>:<purl>[@<version>]
EvidenceID = hash(canonical_evidence_json)
ReasoningID = hash(canonical_reasoning_json)
VEXVerdictID = hash(canonical_vex_json)
ProofBundleID = merkle_root(SBOMEntryID, EvidenceID[], ReasoningID, VEXVerdictID)
GraphRevisionID = merkle_root(nodes[], edges[], policyDigest, feedsDigest, toolchainDigest, paramsDigest)
TrustAnchorID = per-dependency anchor (public key + policy)
1.2 Component Identifiers (bom-ref)
Format: pkg:<ecosystem>/<name>@<version>?sha256=<digest>
Examples:
pkg:maven/org.apache.commons/commons-lang3@3.14.0?sha256=<digest>
pkg:apk/alpine/openssl@3.2.1-r0?sha256=2c0f...54e
pkg:oci/<repo>@sha256:<manifestDigest>
pkg:npm/lodash@4.17.21
Rules:
- Must be stable across regenerations for identical content
- Independent of local paths, build numbers
- Derived from canonical bytes
- Used as CycloneDX bom-ref
1.3 Subject Schema
public sealed record ProofSubject(
string Name, // PURL or canonical URI
IReadOnlyDictionary<string,string> Digest // {"sha256": "...", "sha512": "..."}
);
1.4 SBOM Identity
sbomId = sha256(canonical_sbom_bytes)
1.5 Graph Revision ID (graphRev)
Use GraphRevisionID as the stable snapshot identifier for an artifact's decision graph (facts + derived edges) so receipts, UI/API responses, logs, exports, and replays can be correlated without ambiguity.
Rules:
- Graph revisions are content-addressed: any input change produces a new
GraphRevisionID. - Inputs must be canonicalized (stable ordering, stable casing, UTC timestamps stripped/isolated) before hashing.
- A graph revision must bind to the scan manifest inputs:
sbomDigest,feedsDigest,policyDigest,toolchainDigest, andparamsDigest.
Recommended string format:
graphRevisionId = "grv_sha256:" + sha256(canonical_graph_bytes)
1.6 Proof-of-Integrity Graph (build/runtime ancestry)
Treat provenance as a first-class, append-only graph so "what is running?" can be traced back to "how was it built and attested?"
Minimum model:
- Nodes:
repo,commit,build,sbom,attestation,image,container,host - Edges:
built_from,scanned_with,attests,deployed_as,executes_on,derived_from - IDs: use content digests where possible (image digest, SBOM hash, DSSE hash); stable IDs for non-content entities (run IDs, host IDs)
Operational rules:
- Never delete/overwrite nodes; mark superseded instead.
- Every UI traversal must be backed by API queries over this graph (no UI-only inference).
2. DSSE ENVELOPE STRUCTURES
2.1 Evidence Statement
{
"payloadType": "application/vnd.in-toto+json",
"payload": {
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
"predicateType": "evidence.stella/v1",
"predicate": {
"source": "scanner/feed name",
"sourceVersion": "tool version",
"collectionTime": "2025-12-14T00:00:00Z",
"sbomEntryId": "<SBOMEntryID>",
"vulnerabilityId": "CVE-XXXX-YYYY",
"rawFinding": "<pointer or data>",
"evidenceId": "<EvidenceID>"
}
},
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
}
Signer: Scanner/Ingestor key
2.2 Reasoning Statement
{
"payloadType": "application/vnd.in-toto+json",
"payload": {
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
"predicateType": "reasoning.stella/v1",
"predicate": {
"sbomEntryId": "<SBOMEntryID>",
"evidenceIds": ["<EvidenceID>", ...],
"policyVersion": "v2.3.1",
"inputs": {
"currentEvaluationTime": "2025-12-14T00:00:00Z",
"severityThresholds": {...},
"latticeRules": {...}
},
"intermediateFindings": {},
"reasoningId": "<ReasoningID>"
}
},
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
}
Signer: Policy/Authority key
2.3 VEX Verdict Statement
{
"payloadType": "application/vnd.in-toto+json",
"payload": {
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
"predicateType": "cdx-vex.stella/v1",
"predicate": {
"sbomEntryId": "<SBOMEntryID>",
"vulnerabilityId": "CVE-XXXX-YYYY",
"status": "not_affected|affected|fixed|under_investigation",
"justification": "vulnerable_code_not_in_execute_path",
"policyVersion": "v2.3.1",
"reasoningId": "<ReasoningID>",
"vexVerdictId": "<VEXVerdictID>"
}
},
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
}
Signer: VEXer key or vendor key
2.4 Proof Spine Statement
{
"payloadType": "application/vnd.in-toto+json",
"payload": {
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
"predicateType": "proofspine.stella/v1",
"predicate": {
"sbomEntryId": "<SBOMEntryID>",
"evidenceIds": ["<ID1>", "<ID2>"],
"reasoningId": "<ID>",
"vexVerdictId": "<ID>",
"policyVersion": "v2.3.1",
"proofBundleId": "<ProofBundleID>"
}
},
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
}
Signer: Authority key
2.5 Verdict Receipt Statement (per finding/verdict)
Use a receipt to bind the final surfaced decision to graphRevisionId and to the upstream proof objects (evidence/reasoning/VEX/spine). This is the primary export object for audit kits and benchmarks.
{
"payloadType": "application/vnd.in-toto+json",
"payload": {
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "<ArtifactID>", "digest": {"sha256": "..."}}],
"predicateType": "verdict.stella/v1",
"predicate": {
"graphRevisionId": "<GraphRevisionID>",
"findingKey": {"sbomEntryId": "<SBOMEntryID>", "vulnerabilityId": "CVE-XXXX-YYYY"},
"rule": {"id": "POLICY-RULE-123", "version": "v2.3.1"},
"decision": {"status": "block|warn|pass", "reason": "short human-readable summary"},
"inputs": {"sbomDigest": "sha256:...", "feedsDigest": "sha256:...", "policyDigest": "sha256:..."},
"outputs": {"proofBundleId": "<ProofBundleID>", "reasoningId": "<ReasoningID>", "vexVerdictId": "<VEXVerdictID>"},
"createdAt": "2025-12-14T00:00:00Z"
}
},
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
}
2.6 SBOM Linkage Statement
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{"name": "pkg:npm/lodash@4.17.21", "digest": {"sha256": "...", "sha512": "..."}}
],
"predicateType": "https://stella-ops.org/predicates/sbom-linkage/v1",
"predicate": {
"sbom": {
"id": "<sbomId>",
"format": "CycloneDX",
"specVersion": "1.6",
"mediaType": "application/vnd.cyclonedx+json",
"sha256": "<sha256>",
"location": "oci://... or file://..."
},
"generator": {
"name": "StellaOps.Sbomer",
"version": "x.y.z"
},
"generatedAt": "2025-12-14T00:00:00Z",
"incompleteSubjects": [],
"tags": {
"tenantId": "...",
"projectId": "...",
"pipelineRunId": "..."
}
}
}
3. CYCLONEDX VEX STRUCTURE
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"version": 1,
"vulnerabilities": [
{
"id": "CVE-2024-12345",
"source": {"name": "NVD"},
"analysis": {
"state": "not_affected",
"justification": "vulnerable_code_not_present",
"response": ["will_not_fix"],
"detail": "Linked OpenSSL feature set excludes the vulnerable cipher."
},
"affects": [
{"ref": "pkg:apk/alpine/openssl@3.2.1-r0?sha256=2c0f...54e"}
],
"properties": [
{"name": "evidence.sbomDigest", "value": "sha256:91f2...9a"},
{"name": "evidence.rekorLogID", "value": "425c1d1e..."},
{"name": "reachability.report", "value": "sha256:reacha..."},
{"name": "policy.decision", "value": "TrustGate#R-17.2"}
]
}
]
}
VEX Status Values
not_affected
affected
fixed
under_investigation
VEX Justification Values
vulnerable_code_not_present
vulnerable_code_not_in_execute_path
vulnerable_code_not_configured
vulnerable_code_cannot_be_controlled_by_adversary
component_not_present
inline_mitigations_exist
4. STORAGE SCHEMA
4.1 PostgreSQL Tables
CREATE TABLE sbom_entries (
entry_id UUID PRIMARY KEY,
bom_digest VARCHAR(64) NOT NULL,
purl TEXT NOT NULL,
version TEXT,
artifact_digest VARCHAR(64),
trust_anchor_id UUID,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE dsse_envelopes (
env_id UUID PRIMARY KEY,
entry_id UUID REFERENCES sbom_entries(entry_id),
predicate_type TEXT NOT NULL,
signer_keyid TEXT NOT NULL,
body_hash VARCHAR(64) NOT NULL,
envelope_blob_ref TEXT NOT NULL,
signed_at TIMESTAMPTZ NOT NULL,
INDEX idx_entry_predicate (entry_id, predicate_type)
);
CREATE TABLE spines (
entry_id UUID PRIMARY KEY REFERENCES sbom_entries(entry_id),
bundle_id VARCHAR(64) NOT NULL,
evidence_ids TEXT[] NOT NULL,
reasoning_id VARCHAR(64) NOT NULL,
vex_id VARCHAR(64) NOT NULL,
anchor_id UUID,
policy_version TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE trust_anchors (
anchor_id UUID PRIMARY KEY,
purl_pattern TEXT NOT NULL,
allowed_keyids TEXT[] NOT NULL,
policy_ref TEXT,
revoked_keys TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE rekor_entries (
dsse_sha256 VARCHAR(64) PRIMARY KEY,
log_index BIGINT NOT NULL,
log_id TEXT NOT NULL,
integrated_time BIGINT NOT NULL,
inclusion_proof JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
4.2 Proof Graph Nodes
Node Types:
- Artifact (container image, binary, Helm chart)
- SbomDocument (by sbomId)
- InTotoStatement (by statement hash)
- DsseEnvelope (by envelope hash)
- RekorEntry (by log index/UUID)
- VexStatement (by vex hash)
- Subject (component from SBOM)
Edge Types:
- DESCRIBED_BY: Artifact → SbomDocument
- ATTESTED_BY: SbomDocument → InTotoStatement
- WRAPPED_BY: InTotoStatement → DsseEnvelope
- LOGGED_IN: DsseEnvelope → RekorEntry
- HAS_VEX: Artifact/Subject → VexStatement
- CONTAINS_SUBJECT: InTotoStatement → Subject
- PRODUCES: Build → SBOM
- AFFECTS: VEX → Component
- SIGNED_BY: Envelope → Key
- RECORDED_AT: Envelope → Rekor
5. API CONTRACTS
5.1 Proof Spine API
POST /proofs/:entry/spine
Body: {
"evidenceIds": ["<ID1>", ...],
"reasoningId": "<ID>",
"vexVerdictId": "<ID>",
"policyVersion": "v2.3.1"
}
Response: 201 Created, {"proofBundleId": "..."}
GET /proofs/:entry/receipt
Response: {
"proofBundleId": "...",
"verifiedAt": "2025-12-14T00:00:00Z",
"verifierVersion": "1.0.0",
"anchorId": "...",
"result": "pass|fail",
"details": {...}
}
GET /proofs/:entry/vex
Response: <VEX JSON body>
GET /anchors/:anchor
Response: {
"anchorId": "...",
"purlPattern": "pkg:npm/*",
"allowedKeyids": ["key1", "key2"],
"policyRef": "...",
"revokedKeys": []
}
5.2 Verification API
POST /verify
Body: {
"artifactDigest": "sha256:...",
"sbom": <SBOM JSON or reference>,
"vex": <VEX JSON or reference>,
"signatures": [...],
"logs": [...]
}
Response: {
"artifact": "pkg:oci/...",
"sbomVerified": true,
"vexVerified": true,
"components": [
{
"bomRef": "pkg:...",
"vulnerabilities": [
{
"id": "CVE-...",
"state": "not_affected",
"justification": "..."
}
]
}
]
}
6. CANONICALIZATION RULES
6.1 JSON Canonicalization
1. UTF-8 encoding
2. Sorted keys (lexicographic)
3. No insignificant whitespace
4. No volatile fields beyond semantic need
5. Version schema: evidence.stella/v1, reasoning.stella/v1
6. Deterministic array ordering where semantically unordered
6.2 SBOM Canonicalization
public interface ISbomCanonicalizer
{
byte[] Canonicalize(ReadOnlySpan<byte> rawSbom, string mediaType);
}
public interface IBlobHasher
{
string ComputeSha256Hex(ReadOnlySpan<byte> data);
}
Rules:
- Remove insignificant whitespace
- Sort object keys lexicographically
- Sort arrays deterministically (by bom-ref or purl)
- Strip volatile fields: generation timestamps, tool build IDs, non-deterministic UUIDs
- Convert to internal JSON, then canonicalize
6.3 Subject Extraction
IEnumerable<Subject> ToSubjects(CycloneDxSbom sbom)
{
foreach (var c in sbom.Metadata.Components)
{
if (c.Hashes == null || c.Hashes.Count == 0) continue;
var name = $"pkg:{c.Type}/{c.Name}@{c.Version}";
var dig = c.Hashes
.OrderBy(h => h.Algorithm)
.ToDictionary(
h => h.Algorithm.ToLowerInvariant(),
h => h.Value.ToLowerInvariant()
);
yield return new Subject(name, dig);
}
}
Digest requirements:
- Must have at least sha256 or sha512
- Normalize algorithm keys to lowercase
- Sort subjects by: 1) Name ascending, 2) Algorithm:value pairs
7. REKOR INTEGRATION
7.1 Rekor Entry Structure
{
"dsseSha256": "sha256:...",
"rekor": {
"uuid": "...",
"logIndex": 12345,
"logId": "...",
"integratedTime": 1733736000,
"inclusionProof": {
"rootHash": "...",
"hashes": ["...", "..."],
"checkpoint": "..."
}
}
}
7.2 Offline Update Bundle Structure
/bundle-2025-12-14/
manifest.json # version, created_at, entries[], sha256s
payload.tar.zst # actual DB/indices/feeds
payload.tar.zst.sha256
statement.dsse.json # DSSE-wrapped statement over payload hash
rekor-receipt.json # Rekor v2 inclusion/verification material
7.3 Offline Update DSSE Predicate
{
"predicateType": "https://stella-ops.org/attestations/offline-update/1",
"predicate": {
"offline_manifest_sha256": "sha256:...",
"feeds": [
{
"name": "nvd",
"snapshot_date": "2025-12-14",
"archive_digest": "sha256:..."
}
],
"builder": "ci-workflow-id / git-commit / job-id",
"created_at": "2025-12-14T00:00:00Z",
"oukit_channel": "stable|edge|fips-profile"
}
}
7.4 Verification Sequence (Offline Kit)
1. Validate Cosign signature of tarball
2. Validate offline-manifest.json with JWS signature
3. Verify file digests for all entries (including /attestations/*)
4. Verify DSSE:
- Call StellaOps.Attestor.Verify with:
- offline-update.dsse.json
- offline-update.rekor.json
- local Rekor log snapshot/segment
- Ensure payload digest matches kit tarball + manifest digests
5. Only after all checks pass:
- Swap Scanner's feed pointer to new snapshot
- Emit audit event (kit filename, tarball digest, DSSE digest, Rekor UUID + log index)
8. CRYPTOGRAPHIC SPECIFICATIONS
8.1 Signing Keys & Profiles
Default Profile:
Hash: SHA-256
Signature: Ed25519 or ECDSA P-256
Future Profiles:
GOST R 34.10-2012
eIDAS-compliant algorithms
FIPS 140-2/140-3
SM2/SM3 (Chinese standards)
PQC: Dilithium/Falcon for long-term archives
Key Storage:
KMS/HSM (production)
PKCS#11 for air-gap
Per-environment keysets: dev, staging, prod
Per-role keysets: Authority, VEXer, Evidence Ingestor
8.2 Key Rotation
Rotation Process:
1. Add new allowed_keyids to TrustAnchor
2. Never mutate old DSSE envelopes
3. Publish key material via attestation feed or Rekor-mirror
4. Record all changes in audit log
5. Maintain key version history
8.3 Trust Anchor Structure
{
"trustAnchorId": "UUID",
"purlPattern": "pkg:npm/*",
"allowedKeyids": ["keyid1", "keyid2"],
"allowedPredicateTypes": [
"evidence.stella/v1",
"reasoning.stella/v1",
"cdx-vex.stella/v1"
],
"policyVersion": "v2.3.1",
"revokedKeys": []
}
9. VERIFICATION PIPELINE
9.1 Verification Algorithm
Input: SBOMEntryID or ProofBundleID
Steps:
1. Resolve SBOMEntryID → TrustAnchorID
2. Fetch spine and trust anchor
3. Verify spine DSSE signature against TrustAnchor.allowedKeyids
4. Verify VEX DSSE signature
5. Verify reasoning DSSE signature
6. Verify evidence DSSE signatures
7. Recompute EvidenceIDs from stored canonical evidence
8. Recompute ReasoningID from reasoning
9. Recompute VEXVerdictID from VEX body
10. Recompute ProofBundleID (merkle root) from above
11. Compare all computed IDs to stored IDs
12. If using Rekor:
- Verify log inclusion proof
- Verify payload hashes match local files
13. Emit Receipt
Output: Receipt {
proofBundleId,
verifiedAt,
verifierVersion,
anchorId,
result: "pass|fail",
details: [...]
}
9.2 Receipt Structure
{
"proofBundleId": "sha256:...",
"verifiedAt": "2025-12-14T00:00:00Z",
"verifierVersion": "1.0.0",
"anchorId": "UUID",
"result": "pass",
"checks": [
{
"check": "spine_signature",
"status": "pass",
"keyid": "..."
},
{
"check": "evidence_id_recompute",
"status": "pass",
"expected": "sha256:...",
"actual": "sha256:..."
},
{
"check": "rekor_inclusion",
"status": "pass",
"logIndex": 12345
}
],
"toolDigests": {
"verifier": "sha256:...",
"canonicalizer": "sha256:..."
}
}
10. DETERMINISM CONSTRAINTS
10.1 Non-Negotiable Invariants
1. Immutability of Signed Facts
- DSSE envelopes are append-only
- Never edit or delete content inside signed envelope
- Corrections via superseding (new statement pointing to old)
2. Determinism
- Same {SBOMEntryID, Evidence set, policyVersion} ⇒ same {ReasoningID, VEXVerdictID, ProofBundleID}
- No non-deterministic inputs in ID computation
- No current time, random IDs in verdict logic
3. Traceability
- Every VEX verdict → SBOM entry, evidence blobs, policy snapshot, trust anchor
4. Least Trust/Least Privilege
- Trust always explicit via TrustAnchors + signature verification
- Never "because it's in our DB"
5. Backwards Compatibility
- New code verifies old proofs
- New policies generate new spines, old spines intact
10.2 Temporal Handling
UTC ISO-8601 only
No local time
Timestamps only when semantically required
Derivation from content preferred over wall-clock time
Record evaluation time as explicit input if policy needs it
10.3 Ordering Requirements
Subjects: sorted by Name ascending, then digest keys
Evidence IDs: sorted lexicographically
Keys in JSON: sorted lexicographically
Array elements: stable sort by semantic key (bom-ref, purl)
11. IMPLEMENTATION INTERFACES
11.1 .NET 10 Core Interfaces
// DSSE Signing
public interface IDsseSigner
{
Task<DsseEnvelope> SignAsync(
ReadOnlyMemory<byte> payload,
string payloadType,
string keyProfile,
CancellationToken ct = default
);
}
// Verification
public record DsseEnvelope(
string PayloadType,
byte[] Payload,
Signature[] Signatures
);
public record Signature(
string Keyid,
string Sig,
string? Cert
);
// Subject Extraction
public sealed record ProofSubject(
string Name,
IReadOnlyDictionary<string,string> Digest
);
// Predicate Models
public record SbomLinkagePredicate(
SbomDescriptor Sbom,
GeneratorDescriptor Generator,
DateTimeOffset GeneratedAt,
IReadOnlyList<IncompleteSubject>? IncompleteSubjects,
IReadOnlyDictionary<string,string>? Tags
);
public record EvidencePredicate(
string Source,
string SourceVersion,
DateTimeOffset CollectionTime,
string SbomEntryId,
string? VulnerabilityId,
object RawFinding,
string EvidenceId
);
public record ReasoningPredicate(
string SbomEntryId,
string[] EvidenceIds,
string PolicyVersion,
Dictionary<string,object> Inputs,
Dictionary<string,object>? IntermediateFindings,
string ReasoningId
);
public record VexPredicate(
string SbomEntryId,
string VulnerabilityId,
string Status,
string Justification,
string PolicyVersion,
string ReasoningId,
string VexVerdictId
);
public record ProofSpinePredicate(
string SbomEntryId,
string[] EvidenceIds,
string ReasoningId,
string VexVerdictId,
string PolicyVersion,
string ProofBundleId
);
11.2 Rekor Client Interface
public interface IRekorClient
{
Task<RekorEntry> SubmitDsseAsync(
DsseEnvelope envelope,
CancellationToken ct = default
);
Task<bool> VerifyInclusionAsync(
RekorEntry entry,
byte[] payloadDigest,
byte[] rekorPublicKey,
CancellationToken ct = default
);
}
public record RekorEntry(
string Uuid,
long LogIndex,
string LogId,
long IntegratedTime,
InclusionProof Proof
);
public record InclusionProof(
string RootHash,
string[] Hashes,
string Checkpoint
);
12. CONFIGURATION SCHEMA
12.1 Scanner Offline Kit Config
scanner:
offlineKit:
requireDsse: true
rekorOfflineMode: true
attestationVerifier: https://attestor.internal
trustAnchors:
- anchorId: "UUID"
purlPattern: "pkg:npm/*"
allowedKeyids: ["key1", "key2"]
12.2 Signer Config
signer:
profiles:
default:
algorithm: "SHA256-ED25519"
keyStore: "kms://..."
fips:
algorithm: "SHA256-ECDSA-P256"
keyStore: "hsm://..."
pqc:
algorithm: "SHA256-DILITHIUM3"
keyStore: "kms://..."
12.3 Authority Config
authority:
trustRoots:
- id: "root-ca-1"
publicKey: "..."
validFrom: "2025-01-01T00:00:00Z"
validUntil: "2030-01-01T00:00:00Z"
keystores:
- type: "kms"
url: "aws-kms://..."
region: "us-east-1"
13. ERROR HANDLING
13.1 Ingestion Failures
If SBOM invalid:
- Reject SBOM
- Record DSSE failure attestation:
{
"error": "schema_validation_failed",
"file": "sbom.json",
"system_version": "1.0.0"
}
- Maintain proof trail for "we tried and it failed"
13.2 Missing Digests
If component lacks sha256/sha512:
- Do NOT use as primary subject in proof chain
- Log in "incompleteSubjects" block in predicate
- Expose in UI as "unverifiable component"
13.3 Rekor Failures
If Rekor unavailable:
- Store DSSE envelope locally
- Queue for retry
- Mark proof chain as "rekorStatus: pending"
- Internal-only until Rekor sync succeeds
- Flag in verification results
14. METRICS & OBSERVABILITY
14.1 Pipeline Metrics
sboms_ingested_total
sbom_ingest_errors_total{reason}
evidence_statements_created_total
reasoning_statements_created_total
vex_statements_created_total
proof_spines_created_total
proof_verifications_total{result}
*_duration_seconds (latency histograms per stage)
offlinekit_import_total{status="success|failed_dsse|failed_rekor|failed_cosign"}
offlinekit_attestation_verify_latency_seconds
attestor_rekor_success_total
attestor_rekor_retry_total
rekor_inclusion_latency
14.2 Structured Logging Fields
sbomEntryId
proofBundleId
anchorId
policyVersion
requestId / traceId
rekorUuid
attestationDigest
offlineKitHash
failureReason
15. CI/CD INTEGRATION
15.1 Pipeline Hooks
On SBOM ingest:
- Create/refresh SBOMEntry rows
- Attach TrustAnchor
On scan completion:
- Produce Evidence Statements (DSSE) immediately
On policy evaluation:
- Produce Reasoning + VEX
- Assemble Spine
Release gates:
- Require: GET /proofs/:entry/receipt == PASS
15.2 CLI Exit Codes
0 = no policy violation
1 = policy violation
2 = scanner/system error (distinguish from "found vulns")
15.3 CLI Output Modes
Default: Human-readable summary (3-5 lines)
--output json: Machine-readable with:
- Web UI run page link
- Proof bundle ID
- Rekor/ledger reference
-v / -vv: Verbose details
Document Version: 1.0 Target Platform: .NET 10, PostgreSQL ≥16, Angular v17