- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management. - Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management. - Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support. - Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
7.1 KiB
Evidence Reconciliation
This document describes the evidence reconciliation algorithm implemented in the StellaOps.AirGap.Importer module. The algorithm provides deterministic, lattice-based reconciliation of security evidence from air-gapped bundles.
Overview
Evidence reconciliation is a 5-step pipeline that transforms raw evidence artifacts (SBOMs, attestations, VEX documents) into a unified, content-addressed evidence graph suitable for policy evaluation and audit trails.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Evidence Reconciliation Pipeline │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: Artifact Indexing │
│ ├── EvidenceDirectoryDiscovery │
│ ├── ArtifactIndex (digest-keyed) │
│ └── Digest normalization (sha256:...) │
│ │
│ Step 2: Evidence Collection │
│ ├── SbomCollector (CycloneDX, SPDX) │
│ ├── AttestationCollector (DSSE) │
│ └── Integration with DsseVerifier │
│ │
│ Step 3: Normalization │
│ ├── JsonNormalizer (stable sorting) │
│ ├── Timestamp stripping │
│ └── URI lowercase normalization │
│ │
│ Step 4: Lattice Rules │
│ ├── SourcePrecedenceLattice │
│ ├── VEX merge with precedence │
│ └── Conflict resolution │
│ │
│ Step 5: Graph Emission │
│ ├── EvidenceGraph construction │
│ ├── Deterministic serialization │
│ └── SHA-256 manifest generation │
│ │
└─────────────────────────────────────────────────────────────────┘
Components
Step 1: Artifact Indexing
ArtifactIndex - A digest-keyed index of all artifacts in the evidence bundle.
// Key types
public readonly record struct DigestKey(string Algorithm, string Value);
// Normalization
DigestKey.Parse("sha256:abc123...") → DigestKey("sha256", "abc123...")
EvidenceDirectoryDiscovery - Discovers evidence files from a directory structure.
Expected structure:
evidence/
├── sboms/
│ ├── component-a.cdx.json
│ └── component-b.spdx.json
├── attestations/
│ └── artifact.dsse.json
└── vex/
└── vendor-vex.json
Step 2: Evidence Collection
Parsers:
CycloneDxParser- Parses CycloneDX 1.4/1.5/1.6 formatSpdxParser- Parses SPDX 2.3 formatDsseAttestationParser- Parses DSSE envelopes
Collectors:
SbomCollector- Orchestrates SBOM parsing and indexingAttestationCollector- Orchestrates attestation parsing and verification
Step 3: Normalization
SbomNormalizer applies format-specific normalization:
| Rule | Description |
|---|---|
| Stable JSON sorting | Keys sorted alphabetically (ordinal) |
| Timestamp stripping | Removes created, modified, timestamp fields |
| URI normalization | Lowercases scheme, host, normalizes paths |
| Whitespace normalization | Consistent formatting |
Step 4: Lattice Rules
SourcePrecedenceLattice implements a bounded lattice for VEX source authority:
Vendor (top)
↑
Maintainer
↑
ThirdParty
↑
Unknown (bottom)
Lattice Properties (verified by property-based tests):
- Commutativity:
Join(a, b) = Join(b, a) - Associativity:
Join(Join(a, b), c) = Join(a, Join(b, c)) - Idempotence:
Join(a, a) = a - Absorption:
Join(a, Meet(a, b)) = a
Conflict Resolution Order:
- Higher precedence source wins
- More recent timestamp wins (when same precedence)
- Status priority: NotAffected > Fixed > UnderInvestigation > Affected > Unknown
Step 5: Graph Emission
EvidenceGraph - A content-addressed graph of reconciled evidence:
public sealed record EvidenceGraph
{
public required string Version { get; init; }
public required string DigestAlgorithm { get; init; }
public required string RootDigest { get; init; }
public required IReadOnlyList<EvidenceNode> Nodes { get; init; }
public required IReadOnlyList<EvidenceEdge> Edges { get; init; }
public required DateTimeOffset GeneratedAt { get; init; }
}
Determinism guarantees:
- Nodes sorted by digest (ordinal)
- Edges sorted by (source, target, type)
- SHA-256 manifest includes content hash
- Reproducible across runs with same inputs
Integration
CLI Usage
# Verify offline evidence bundle
stellaops verify offline \
--evidence-dir /evidence \
--artifact sha256:def456... \
--policy verify-policy.yaml
API
// Reconcile evidence
var reconciler = new EvidenceReconciler(options);
var graph = await reconciler.ReconcileAsync(evidenceDir, cancellationToken);
// Verify determinism
var hash1 = graph.ComputeHash();
var graph2 = await reconciler.ReconcileAsync(evidenceDir, cancellationToken);
var hash2 = graph2.ComputeHash();
Debug.Assert(hash1 == hash2); // Always true
Testing
Golden-File Tests
Test fixtures in tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/Fixtures/:
cyclonedx-sample.json- CycloneDX 1.5 samplespdx-sample.json- SPDX 2.3 sampledsse-attestation-sample.json- DSSE envelope sample
Property-Based Tests
SourcePrecedenceLatticePropertyTests verifies:
- Lattice algebraic properties (commutativity, associativity, idempotence, absorption)
- Ordering properties (antisymmetry, transitivity, reflexivity)
- Bound properties (join is LUB, meet is GLB)
- Merge determinism
Related Documents
- Air-Gap Module Architecture (pending)
- DSSE Verification (if exists)
- Offline Kit Import Flow