# Facet Sealing Architecture > **Ownership:** Scanner Guild, Policy Guild > **Audience:** Service owners, platform engineers, security architects > **Related:** [Platform Architecture](../platform/architecture-overview.md), [Scanner Architecture](../scanner/architecture.md), [Replay Architecture](../replay/architecture.md), [Policy Engine](../policy/architecture.md) This dossier describes the Facet Sealing subsystem, which provides cryptographically sealed manifests for logical slices of container images, enabling fine-grained drift detection, per-facet quota enforcement, and deterministic change tracking. --- ## 1. Overview A **Facet** is a declared logical slice of a container image representing a cohesive set of files with shared characteristics: | Facet Type | Description | Examples | |------------|-------------|----------| | `os` | Operating system packages | `/var/lib/dpkg/**`, `/var/lib/rpm/**` | | `lang/` | Language-specific dependencies | `node_modules/**`, `site-packages/**`, `vendor/**` | | `binary` | Native binaries and shared libraries | `/usr/bin/*`, `/lib/**/*.so*` | | `config` | Configuration files | `/etc/**`, `*.conf`, `*.yaml` | | `custom` | User-defined patterns | Project-specific paths | Each facet can be individually **sealed** (cryptographic snapshot) and monitored for **drift** (changes between seals). --- ## 2. System Landscape ```mermaid graph TD subgraph Scanner["Scanner Services"] FE[FacetExtractor] FH[FacetHasher] MB[MerkleBuilder] end subgraph Storage["Facet Storage"] FS[(PostgreSQL
facet_seals)] FC[(CAS
facet_manifests)] end subgraph Policy["Policy & Enforcement"] DC[DriftCalculator] QE[QuotaEnforcer] AV[AdmissionValidator] end subgraph Signing["Attestation"] DS[DSSE Signer] AT[Attestor] end subgraph CLI["CLI & Integration"] SealCmd[stella seal] DriftCmd[stella drift] VexCmd[stella vex gen] Zastava[Zastava Webhook] end FE --> FH FH --> MB MB --> DS DS --> FS DS --> FC FS --> DC DC --> QE QE --> AV AV --> Zastava SealCmd --> FE DriftCmd --> DC VexCmd --> DC ``` --- ## 3. Core Data Models ### 3.1 FacetDefinition Declares a facet with its extraction patterns and quota constraints: ```csharp public sealed record FacetDefinition { public required string FacetId { get; init; } // e.g., "os", "lang/node", "binary" public required FacetType Type { get; init; } // OS, LangNode, LangPython, Binary, Config, Custom public required ImmutableArray IncludeGlobs { get; init; } public ImmutableArray ExcludeGlobs { get; init; } = []; public FacetQuota? Quota { get; init; } } public enum FacetType { OS, LangNode, LangPython, LangGo, LangRust, LangJava, LangDotNet, Binary, Config, Custom } ``` ### 3.2 FacetManifest Per-facet file manifest with Merkle root: ```csharp public sealed record FacetManifest { public required string FacetId { get; init; } public required FacetType Type { get; init; } public required ImmutableArray Files { get; init; } public required string MerkleRoot { get; init; } // SHA-256 hex public required int FileCount { get; init; } public required long TotalBytes { get; init; } public required DateTimeOffset ExtractedAt { get; init; } public required string ExtractorVersion { get; init; } } public sealed record FacetFileEntry { public required string Path { get; init; } // Normalized POSIX path public required string ContentHash { get; init; } // SHA-256 hex public required long Size { get; init; } public required string Mode { get; init; } // POSIX mode string "0644" public required DateTimeOffset ModTime { get; init; } // Normalized to UTC } ``` ### 3.3 FacetSeal DSSE-signed seal combining manifest with metadata: ```csharp public sealed record FacetSeal { public required Guid SealId { get; init; } public required string ImageRef { get; init; } // registry/repo:tag@sha256:... public required string ImageDigest { get; init; } // sha256:... public required FacetManifest Manifest { get; init; } public required DateTimeOffset SealedAt { get; init; } public required string SealedBy { get; init; } // Identity/service public required FacetQuota? AppliedQuota { get; init; } public required DsseEnvelope Envelope { get; init; } } ``` ### 3.4 FacetQuota Per-facet change budget: ```csharp public sealed record FacetQuota { public required string FacetId { get; init; } public double MaxChurnPercent { get; init; } = 5.0; // 0-100 public int MaxChangedFiles { get; init; } = 50; public int MaxAddedFiles { get; init; } = 25; public int MaxRemovedFiles { get; init; } = 10; public QuotaAction OnExceed { get; init; } = QuotaAction.Warn; } public enum QuotaAction { Warn, // Log warning, allow admission Block, // Reject admission RequireVex // Require VEX justification before admission } ``` ### 3.5 FacetDrift Drift calculation result between two seals: ```csharp public sealed record FacetDrift { public required string FacetId { get; init; } public required Guid BaselineSealId { get; init; } public required Guid CurrentSealId { get; init; } public required ImmutableArray Added { get; init; } public required ImmutableArray Removed { get; init; } public required ImmutableArray Modified { get; init; } public required DriftScore Score { get; init; } public required QuotaVerdict QuotaVerdict { get; init; } } public sealed record DriftEntry { public required string Path { get; init; } public string? OldHash { get; init; } public string? NewHash { get; init; } public long? OldSize { get; init; } public long? NewSize { get; init; } public DriftCause Cause { get; init; } = DriftCause.Unknown; } public enum DriftCause { Unknown, PackageUpdate, ConfigChange, BinaryRebuild, NewDependency, RemovedDependency, SecurityPatch } public sealed record DriftScore { public required int TotalChanges { get; init; } public required double ChurnPercent { get; init; } public required int AddedCount { get; init; } public required int RemovedCount { get; init; } public required int ModifiedCount { get; init; } } public sealed record QuotaVerdict { public required bool Passed { get; init; } public required ImmutableArray Violations { get; init; } public required QuotaAction RecommendedAction { get; init; } } public sealed record QuotaViolation { public required string QuotaField { get; init; } // e.g., "MaxChurnPercent" public required double Limit { get; init; } public required double Actual { get; init; } public required string Message { get; init; } } ``` --- ## 4. Component Architecture ### 4.1 FacetExtractor Extracts file entries from container images based on facet definitions: ```csharp public interface IFacetExtractor { Task ExtractAsync( string imageRef, FacetDefinition definition, CancellationToken ct = default); Task> ExtractAllAsync( string imageRef, ImmutableArray definitions, CancellationToken ct = default); } ``` Implementation notes: - Uses existing `ISurfaceReader` for container layer traversal - Normalizes paths to POSIX format (forward slashes, no trailing slashes) - Computes SHA-256 content hashes for each file - Normalizes timestamps to UTC, mode to POSIX string - Sorts files lexicographically for deterministic ordering ### 4.2 FacetHasher Computes Merkle tree for facet file entries: ```csharp public interface IFacetHasher { FacetMerkleResult ComputeMerkle(ImmutableArray files); } public sealed record FacetMerkleResult { public required string Root { get; init; } public required ImmutableArray LeafHashes { get; init; } public required ImmutableArray Proof { get; init; } } ``` Implementation notes: - Leaf hash = SHA-256(path || contentHash || size || mode) - Binary Merkle tree with lexicographic leaf ordering - Empty facet produces well-known empty root hash - Proof enables verification of individual file membership ### 4.3 FacetSealStore PostgreSQL storage for sealed facet manifests: ```sql -- Core seal storage CREATE TABLE facet_seals ( seal_id UUID PRIMARY KEY, tenant TEXT NOT NULL, image_ref TEXT NOT NULL, image_digest TEXT NOT NULL, facet_id TEXT NOT NULL, facet_type TEXT NOT NULL, merkle_root TEXT NOT NULL, file_count INTEGER NOT NULL, total_bytes BIGINT NOT NULL, sealed_at TIMESTAMPTZ NOT NULL, sealed_by TEXT NOT NULL, quota_json JSONB, manifest_cas TEXT NOT NULL, -- CAS URI to full manifest dsse_envelope JSONB NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_facet_seal UNIQUE (tenant, image_digest, facet_id) ); CREATE INDEX ix_facet_seals_image ON facet_seals (tenant, image_digest); CREATE INDEX ix_facet_seals_merkle ON facet_seals (merkle_root); -- Drift history CREATE TABLE facet_drift_history ( drift_id UUID PRIMARY KEY, tenant TEXT NOT NULL, baseline_seal_id UUID NOT NULL REFERENCES facet_seals(seal_id), current_seal_id UUID NOT NULL REFERENCES facet_seals(seal_id), facet_id TEXT NOT NULL, drift_score_json JSONB NOT NULL, quota_verdict_json JSONB NOT NULL, computed_at TIMESTAMPTZ NOT NULL, CONSTRAINT uq_drift_pair UNIQUE (baseline_seal_id, current_seal_id) ); ``` ### 4.4 DriftCalculator Computes drift between baseline and current seals: ```csharp public interface IDriftCalculator { Task CalculateAsync( Guid baselineSealId, Guid currentSealId, CancellationToken ct = default); Task> CalculateAllAsync( string imageDigestBaseline, string imageDigestCurrent, CancellationToken ct = default); } ``` Implementation notes: - Retrieves manifests from CAS via seal metadata - Performs set difference operations on file paths - Detects modifications via content hash comparison - Attributes drift causes where determinable (e.g., package manager metadata) ### 4.5 QuotaEnforcer Evaluates drift against quota constraints: ```csharp public interface IQuotaEnforcer { QuotaVerdict Evaluate(FacetDrift drift, FacetQuota quota); Task> EvaluateAllAsync( ImmutableArray drifts, ImmutableDictionary quotas, CancellationToken ct = default); } ``` ### 4.6 AdmissionValidator Zastava webhook integration for admission control: ```csharp public interface IFacetAdmissionValidator { Task ValidateAsync( AdmissionRequest request, CancellationToken ct = default); } public sealed record AdmissionResult { public required bool Allowed { get; init; } public string? Message { get; init; } public ImmutableArray Violations { get; init; } = []; public string? RequiredVexStatement { get; init; } } ``` --- ## 5. DSSE Envelope Structure Facet seals use DSSE (Dead Simple Signing Envelope) for cryptographic binding: ```json { "payloadType": "application/vnd.stellaops.facet-seal.v1+json", "payload": "", "signatures": [ { "keyid": "sha256:abc123...", "sig": "" } ] } ``` Payload structure (canonical JSON, RFC 8785): ```json { "_type": "https://stellaops.io/FacetSeal/v1", "facetId": "os", "facetType": "OS", "imageDigest": "sha256:abc123...", "imageRef": "registry.example.com/app:v1.2.3", "manifest": { "extractedAt": "2026-01-05T10:00:00.000Z", "extractorVersion": "1.0.0", "fileCount": 1234, "files": [ { "contentHash": "sha256:...", "mode": "0644", "modTime": "2026-01-01T00:00:00.000Z", "path": "/etc/os-release", "size": 256 } ], "merkleRoot": "sha256:def456...", "totalBytes": 1048576 }, "quota": { "maxAddedFiles": 25, "maxChangedFiles": 50, "maxChurnPercent": 5.0, "maxRemovedFiles": 10, "onExceed": "Warn" }, "sealId": "550e8400-e29b-41d4-a716-446655440000", "sealedAt": "2026-01-05T10:05:00.000Z", "sealedBy": "scanner-worker-01" } ``` --- ## 6. Default Facet Definitions Standard facet definitions applied when no custom configuration is provided: ```yaml # Default facet configuration facets: - facetId: os type: OS includeGlobs: - /var/lib/dpkg/** - /var/lib/rpm/** - /var/lib/pacman/** - /var/lib/apk/** - /var/cache/apt/** - /etc/apt/** - /etc/yum.repos.d/** excludeGlobs: - "**/*.log" quota: maxChurnPercent: 5.0 maxChangedFiles: 100 onExceed: Warn - facetId: lang/node type: LangNode includeGlobs: - "**/node_modules/**" - "**/package.json" - "**/package-lock.json" - "**/yarn.lock" - "**/pnpm-lock.yaml" quota: maxChurnPercent: 10.0 maxChangedFiles: 500 onExceed: RequireVex - facetId: lang/python type: LangPython includeGlobs: - "**/site-packages/**" - "**/dist-packages/**" - "**/requirements.txt" - "**/Pipfile.lock" - "**/poetry.lock" quota: maxChurnPercent: 10.0 maxChangedFiles: 200 onExceed: Warn - facetId: lang/go type: LangGo includeGlobs: - "**/go.mod" - "**/go.sum" - "**/vendor/**" quota: maxChurnPercent: 15.0 maxChangedFiles: 100 onExceed: Warn - facetId: binary type: Binary includeGlobs: - /usr/bin/* - /usr/sbin/* - /bin/* - /sbin/* - /usr/lib/**/*.so* - /lib/**/*.so* - /usr/local/bin/* excludeGlobs: - "**/*.py" - "**/*.sh" quota: maxChurnPercent: 2.0 maxChangedFiles: 20 onExceed: Block - facetId: config type: Config includeGlobs: - /etc/** - "**/*.conf" - "**/*.cfg" - "**/*.ini" - "**/*.yaml" - "**/*.yml" - "**/*.json" excludeGlobs: - /etc/passwd - /etc/shadow - /etc/group - "**/*.log" quota: maxChurnPercent: 20.0 maxChangedFiles: 50 onExceed: Warn ``` --- ## 7. Integration Points ### 7.1 Scanner Integration Scanner invokes facet extraction during scan: ```csharp // In ScanOrchestrator var facetDefs = await _facetConfigLoader.LoadAsync(scanRequest.FacetConfig, ct); var manifests = await _facetExtractor.ExtractAllAsync(imageRef, facetDefs, ct); foreach (var manifest in manifests) { var seal = await _facetSealer.SealAsync(manifest, scanRequest, ct); await _facetSealStore.SaveAsync(seal, ct); } ``` ### 7.2 CLI Integration ```bash # Seal all facets for an image stella seal myregistry.io/app:v1.2.3 --output seals.json # Seal specific facets stella seal myregistry.io/app:v1.2.3 --facet os --facet lang/node # Check drift between two image versions stella drift myregistry.io/app:v1.2.3 myregistry.io/app:v1.2.4 --format json # Generate VEX from drift stella vex gen --from-drift myregistry.io/app:v1.2.3 myregistry.io/app:v1.2.4 ``` ### 7.3 Zastava Webhook Integration ```csharp // In FacetAdmissionValidator public async Task ValidateAsync(AdmissionRequest request, CancellationToken ct) { // Find baseline seal (latest approved) var baseline = await _sealStore.GetLatestApprovedAsync(request.ImageRef, ct); if (baseline is null) return AdmissionResult.Allowed("No baseline seal found, skipping facet check"); // Extract current facets var currentManifests = await _extractor.ExtractAllAsync(request.ImageRef, _defaultFacets, ct); // Calculate drift for each facet var drifts = new List(); foreach (var manifest in currentManifests) { var baselineSeal = baseline.FirstOrDefault(s => s.FacetId == manifest.FacetId); if (baselineSeal is not null) { var drift = await _driftCalculator.CalculateAsync(baselineSeal, manifest, ct); drifts.Add(drift); } } // Evaluate quotas var violations = new List(); QuotaAction maxAction = QuotaAction.Warn; foreach (var drift in drifts) { var verdict = _quotaEnforcer.Evaluate(drift, drift.AppliedQuota); if (!verdict.Passed) { violations.AddRange(verdict.Violations); if (verdict.RecommendedAction > maxAction) maxAction = verdict.RecommendedAction; } } return maxAction switch { QuotaAction.Block => AdmissionResult.Denied(violations), QuotaAction.RequireVex => AdmissionResult.RequiresVex(violations), _ => AdmissionResult.Allowed(violations) }; } ``` --- ## 8. Observability ### 8.1 Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `facet_seal_total` | Counter | `tenant`, `facet_type`, `status` | Total seals created | | `facet_seal_duration_seconds` | Histogram | `facet_type` | Time to create seal | | `facet_drift_score` | Gauge | `tenant`, `facet_id`, `image` | Current drift score | | `facet_quota_violations_total` | Counter | `tenant`, `facet_id`, `quota_field` | Quota violations | | `facet_admission_decisions_total` | Counter | `tenant`, `decision`, `facet_id` | Admission decisions | ### 8.2 Traces ``` facet.extract - Facet file extraction from image facet.hash - Merkle tree computation facet.seal - DSSE signing facet.drift.compute - Drift calculation facet.quota.evaluate - Quota enforcement facet.admission - Admission validation ``` ### 8.3 Logs Structured log fields: - `facetId`: Facet identifier - `imageRef`: Container image reference - `imageDigest`: Image content digest - `merkleRoot`: Facet Merkle root - `driftScore`: Computed drift percentage - `quotaVerdict`: Pass/fail status --- ## 9. Security Considerations 1. **Signature Verification**: All seals must be DSSE-signed with keys managed by Authority service 2. **Tenant Isolation**: Seals are scoped to tenants; cross-tenant access is prohibited 3. **Immutability**: Once created, seals cannot be modified; only superseded by new seals 4. **Audit Trail**: All seal operations are logged with correlation IDs 5. **Key Rotation**: Signing keys support rotation; old signatures remain valid with archived keys --- ## 10. References - [DSSE Specification](https://github.com/secure-systems-lab/dsse) - [RFC 8785 - JSON Canonicalization](https://tools.ietf.org/html/rfc8785) - [Scanner Architecture](../scanner/architecture.md) - [Attestor Architecture](../attestor/architecture.md) - [Policy Engine Architecture](../policy/architecture.md) - [Replay Architecture](../replay/architecture.md) --- *Last updated: 2026-01-05*