27 KiB
Here’s a clear, SBOM‑first blueprint you can drop into Stella Ops without extra context.
SBOM‑first spine (with attestations) — the short, practical version
Why this matters (plain English)
- SBOMs (CycloneDX/SPDX) = a complete parts list of your software.
- Attestations (in‑toto + DSSE) = tamper‑evident receipts proving who did what, to which artifact, when, and how.
- Determinism = if you re‑scan tomorrow, you get the same result for the same inputs.
- Explainability = every risk decision links back to evidence you can show to auditors/customers.
Core pipeline (modules & responsibilities)
- Scan (Scanner)
- Inputs: container image / dir / repo.
- Outputs: raw facts (packages, files, symbols), and a Scan‑Evidence attestation (DSSE‑wrapped in‑toto statement).
- Must support offline feeds (bundle CVE/NVD/OSV/vendor advisories).
- Sbomer
-
Normalizes raw facts → canonical SBOM (CycloneDX or SPDX) with:
- PURLs, license info, checksums, build‑IDs (ELF/PE/Mach‑O), source locations.
-
Emits SBOM‑Produced attestation linking SBOM ↔ image digest.
- Authority
- Verifies every attestation chain (Sigstore/keys; PQ-ready option later).
- Stamps Policy‑Verified attestation (who approved, policy hash, inputs).
- Persists trust‑log: signatures, cert chains, Rekor‑like index (mirrorable offline).
- Graph Store (Canonical Graph)
- Ingests SBOM, vulnerabilities, reachability facts, VEX statements.
- Preserves evidence links (edge predicates: “found‑by”, “reachable‑via”, “proven‑by”).
- Enables deterministic replay (snapshot manifests: feeds+rules+hashes).
Stable APIs (keep these boundaries sharp)
- /scan → start scan; returns Evidence ID + attestation ref.
- /sbom → get canonical SBOM (by image digest or Evidence ID).
- /attest → submit/fetch attestations; verify chain; returns trust‑proof.
- /vex‑gate → policy decision: allow / warn / block with proof bundle.
- /diff → SBOM↔SBOM + SBOM↔runtime diffs (see below).
- /unknowns → create/list/resolve Unknowns (signals needing human/vendor input).
Design notes:
- All responses include
decision,explanation,evidence[],hashes,clock. - Support air‑gap: all endpoints operate on local bundles (ZIP/TAR with SBOM+attestations+feeds).
Determinism & “Unknowns” (noise‑killer loop)
Smart diffs
- SBOM↔SBOM: detect added/removed/changed components (by PURL+version+hash).
- SBOM↔runtime: prove reachability (e.g., symbol/function use, loaded libs, process maps).
- Score only on provable paths; gate on VEX (vendor/exploitability statements).
Unknowns handler
-
Any unresolved signal (ambiguous CVE mapping, stripped binary, unverified vendor VEX) → Unknowns queue:
- SLA, owner, evidence snapshot, audit trail.
- State machine:
new → triage → vendor‑query → verified → closed. - Every VEX or vendor reply becomes an attestation; decisions re‑evaluated deterministically.
What to store (so you can explain every decision)
-
Artifacts: image digest, SBOM hash, feed versions, rule set hash.
-
Proofs: DSSE envelopes, signatures, certs, inclusion proofs (Rekor‑style).
-
Predicates (edges):
contains(component),vulnerable_to(cve),reachable_via(callgraph|runtime),overridden_by(vex),verified_by(authority),derived_from(scan-evidence).
-
Why‑strings: human‑readable proof trails (1–3 sentences) output with every decision.
Minimal policies that work on day 1
- Block only when:
vuln.severity ≥ HighANDreachable == trueANDno VEX allows. - Warn when:
High/Criticalbutreachable == unknown→ route to Unknowns with SLA. - Allow when:
Low/MediumOR VEX saysnot_affected(trusted signer + policy).
Offline/air‑gap bundle format (zip)
/bundle/
feeds/ (NVD, OSV, vendor) + manifest.json (hashes, timestamps)
sboms/ imageDigest.json
attestations/ *.jsonl (DSSE)
proofs/ rekor/ merkle.json
policy/ lattice.json
replay/ inputs.lock (content‑hashes of everything above)
- Every API accepts
?bundle=/path/to/bundle.zip. - Replay:
inputs.lockguarantees deterministic re‑evaluation.
.NET 10 implementation sketch (pragmatic)
- Contracts:
StellaOps.Contracts.*(Scan, Attest, VexGate, Diff, Unknowns). - Attestations:
StellaOps.Attest.Dsse(IEnvelope, IStatement); pluggable crypto (FIPS/GOST/SM/PQ). - SBOM:
StellaOps.Sbom(CycloneDX/SPDX models + mappers; PURL utilities). - Graph:
StellaOps.Graph(EF Core 9/10 over Mongo/Postgres; edge predicates as enums + JSON evidence). - Policy/Lattice:
StellaOps.Policy.Lattice(pure functions over graph snapshots; produce Decision+Why). - Unknowns:
StellaOps.Unknowns(aggregate root; SLA timers; audit events). - CLI:
stella scan|sbom|attest|vex-gate|diff|unknownswith--bundle.
Key patterns:
- All public methods return
(Result<T> value, Proof proof). - Hash everything (inputs, rules, feeds) and bake into
Proof. - Emit DSSE envelopes at each hop; verify on ingest; never trust unauthenticated input.
Next steps (week‑one checklist)
- Define JSON schemas for: Decision (with Why+Proof), Unknown, DiffResult.
- Stand up /scan, /sbom, /attest, /vex‑gate skeletons returning mock Proofs.
- Implement SBOM↔SBOM diff (PURL+version+hash) and wire to /diff.
- Create bundle.zip reader/writer +
inputs.lock. - Add Authority.Verify() with DSSE verification + trust‑store.
- Ship a basic policy.lattice.json (3 rules above) and e2e test: image → allow/block with proofs.
If you want, I can generate:
- The exact C# interfaces/classes (namespaces + DTOs),
- JSON Schemas for Decision/Proof/Unknowns,
- A seed
bundle.ziplayout with fake data for local e2e. Cool, let’s turn that blueprint into something your devs can actually build from.
Below is a detailed, opinionated implementation plan you can paste into an engineering doc and assign as epics / tickets.
0. Solution layout (concrete)
Repo structure
/stella-ops
/src
StellaOps.Contracts // DTOs, API contracts, JSON schemas
StellaOps.Domain // Core domain types (ArtifactId, Proof, Decision, etc.)
StellaOps.Attest // DSSE envelopes, in-toto statements, signing/verification
StellaOps.Sbom // SBOM models + normalization
StellaOps.Graph // Graph store, entities, queries
StellaOps.Policy // Policy engine (lattice evaluation)
StellaOps.WebApi // HTTP APIs: /scan, /sbom, /attest, /vex-gate, /diff, /unknowns
StellaOps.Cli // `stella` CLI, offline bundles
/tests
StellaOps.Tests.Unit
StellaOps.Tests.Integration
StellaOps.Tests.E2E
Baseline tech assumptions
- Runtime: .NET (8+; you can call it “.NET 10” in your roadmap).
- API: ASP.NET Core minimal APIs.
- DB: Postgres (via EF Core) for graph + unknowns + metadata.
- Storage: local filesystem / S3-compatible for bundle zips, scanner DB caches.
- External scanners: Trivy / Grype / Syft (invoked via CLI with deterministic config).
1. Core domain & shared contracts (Phase 1)
Goal: Have a stable core domain + contracts that all teams can build against.
1.1 Core domain types (StellaOps.Domain)
Implement:
public readonly record struct Digest(string Algorithm, string Value); // e.g. ("sha256", "abcd...")
public readonly record struct ArtifactRef(string Kind, string Value);
// Kind: "container-image", "file", "package", "sbom", etc.
public readonly record struct EvidenceId(Guid Value);
public readonly record struct AttestationId(Guid Value);
public enum PredicateType
{
ScanEvidence,
SbomProduced,
PolicyVerified,
VulnerabilityFinding,
ReachabilityFinding,
VexStatement
}
public sealed class Proof
{
public string ProofId { get; init; } = default!;
public Digest InputsLock { get; init; } = default!; // hash of feeds+rules+sbom bundle
public DateTimeOffset EvaluatedAt { get; init; }
public IReadOnlyList<string> EvidenceIds { get; init; } = Array.Empty<string>();
public IReadOnlyDictionary<string,string> Meta { get; init; } = new Dictionary<string,string>();
}
1.2 Attestation model (StellaOps.Attest)
Implement DSSE + in‑toto abstractions:
public sealed class DsseEnvelope
{
public string PayloadType { get; init; } = default!;
public string Payload { get; init; } = default!; // base64url(JSON)
public IReadOnlyList<DsseSignature> Signatures { get; init; } = Array.Empty<DsseSignature>();
}
public sealed class DsseSignature
{
public string KeyId { get; init; } = default!;
public string Sig { get; init; } = default!; // base64url
}
public interface IStatement<out TPredicate>
{
string Type { get; } // in-toto type URI
string PredicateType { get; } // URI or enum -> string
TPredicate Predicate { get; }
string Subject { get; } // e.g., image digest
}
Attestation services:
public interface IAttestationSigner
{
Task<DsseEnvelope> SignAsync<TPredicate>(IStatement<TPredicate> statement, CancellationToken ct);
}
public interface IAttestationVerifier
{
Task VerifyAsync(DsseEnvelope envelope, CancellationToken ct);
}
1.3 Decision & VEX-gate contracts (StellaOps.Contracts)
public enum GateDecisionKind
{
Allow,
Warn,
Block
}
public sealed class GateDecision
{
public GateDecisionKind Decision { get; init; }
public string Reason { get; init; } = default!; // short human-readable
public Proof Proof { get; init; } = default!;
public IReadOnlyList<string> Evidence { get; init; } = Array.Empty<string>(); // EvidenceIds / AttestationIds
}
public sealed class VexGateRequest
{
public ArtifactRef Artifact { get; init; }
public string? Environment { get; init; } // "prod", "staging", cluster id, etc.
public string? BundlePath { get; init; } // optional offline bundle path
}
Acceptance criteria
- Shared projects compile.
- No service references each other directly (only via Contracts + Domain).
- Example test that serializes/deserializes GateDecision and DsseEnvelope using System.Text.Json.
2. SBOM pipeline (Scanner → Sbomer) (Phase 2)
Goal: For a container image, produce a canonical SBOM + attestation deterministically.
2.1 Scanner integration (StellaOps.WebApi + StellaOps.Cli)
API contract (/scan)
public sealed class ScanRequest
{
public string SourceType { get; init; } = default!; // "container-image" | "directory" | "git-repo"
public string Locator { get; init; } = default!; // e.g. "registry/myapp:1.2.3"
public bool IncludeFiles { get; init; } = true;
public bool IncludeLicenses { get; init; } = true;
public string? BundlePath { get; init; } // for offline data
}
public sealed class ScanResponse
{
public EvidenceId EvidenceId { get; init; }
public AttestationId AttestationId { get; init; }
public Digest ArtifactDigest { get; init; } = default!;
}
Implementation steps
- Scanner abstraction
public interface IArtifactScanner
{
Task<ScanResult> ScanAsync(ScanRequest request, CancellationToken ct);
}
public sealed class ScanResult
{
public ArtifactRef Artifact { get; init; } = default!;
public Digest ArtifactDigest { get; init; } = default!;
public IReadOnlyList<DiscoveredPackage> Packages { get; init; } = Array.Empty<DiscoveredPackage>();
public IReadOnlyList<DiscoveredFile> Files { get; init; } = Array.Empty<DiscoveredFile>();
}
- CLI wrapper (Trivy/Grype/Syft):
-
Implement
SyftScanner : IArtifactScanner:-
Invoke external CLI with fixed flags.
-
Use JSON output mode.
-
Resolve CLI path from config.
-
Ensure deterministic:
- Disable auto-updating DB.
- Use a local DB path versioned and optionally included into bundle.
-
-
Write parsing code Syft →
ScanResult. -
Add retry & clear error mapping (timeout, auth error, network error).
- /scan endpoint
- Validate request.
- Call
IArtifactScanner.ScanAsync. - Build a
ScanEvidencepredicate:
public sealed class ScanEvidencePredicate
{
public ArtifactRef Artifact { get; init; } = default!;
public Digest ArtifactDigest { get; init; } = default!;
public DateTimeOffset ScannedAt { get; init; }
public string ScannerName { get; init; } = default!;
public string ScannerVersion { get; init; } = default!;
public IReadOnlyList<DiscoveredPackage> Packages { get; init; } = Array.Empty<DiscoveredPackage>();
}
-
Build in‑toto statement for predicate.
-
Call
IAttestationSigner.SignAsync, persist:- Raw envelope to
attestationstable. - Map to
EvidenceId+AttestationId.
- Raw envelope to
Acceptance criteria
-
Given a fixed image and fixed scanner DB, repeated
/scancalls produce identical:ScanResult(up to ordering).ScanEvidencepayload.InputsLockproof hash (once implemented).
-
E2E test: run scan on a small public image in CI using a pre-bundled scanner DB.
2.2 Sbomer (StellaOps.Sbom + /sbom)
Goal: Normalize ScanResult into a canonical SBOM (CycloneDX/SPDX) + emit SBOM attestation.
Models
Create neutral SBOM model (internal):
public sealed class CanonicalComponent
{
public string Name { get; init; } = default!;
public string Version { get; init; } = default!;
public string Purl { get; init; } = default!;
public string? License { get; init; }
public Digest Digest { get; init; } = default!;
public string? SourceLocation { get; init; } // file path, layer info
}
public sealed class CanonicalSbom
{
public string SbomId { get; init; } = default!;
public ArtifactRef Artifact { get; init; } = default!;
public Digest ArtifactDigest { get; init; } = default!;
public IReadOnlyList<CanonicalComponent> Components { get; init; } = Array.Empty<CanonicalComponent>();
public DateTimeOffset CreatedAt { get; init; }
public string Format { get; init; } = "CycloneDX-JSON-1.5"; // default
}
Sbomer service
public interface ISbomer
{
CanonicalSbom FromScan(ScanResult scan);
string ToCycloneDxJson(CanonicalSbom sbom);
string ToSpdxJson(CanonicalSbom sbom);
}
Implementation details:
-
Map OS/deps to PURLs (use existing PURL libs or implement minimal helpers).
-
Stable ordering:
- Sort components by
PurlthenVersionbefore serialization.
- Sort components by
-
Hash the SBOM JSON →
Digest(e.g.,Digest("sha256", "...")).
SBOM attestation & /sbom endpoint
-
For an
ArtifactRef(orScanEvidenceEvidenceId):- Fetch latest
ScanResultfrom DB. - Call
ISbomer.FromScan. - Serialize to CycloneDX.
- Emit
SbomProducedpredicate & DSSE envelope. - Persist SBOM JSON blob & link to artifact.
- Fetch latest
Acceptance criteria
-
Same
ScanResultalways produces bit-identical SBOM JSON. -
Unit tests verifying:
- PURL mapping correctness.
- Stable ordering.
-
/sbomendpoint can:- Build SBOM from scan.
- Return existing SBOM if already generated (idempotence).
3. Attestation Authority & trust log (Phase 3)
Goal: Verify all attestations, store them with a trust log, and produce PolicyVerified attestations.
3.1 Authority service (StellaOps.Attest + StellaOps.WebApi)
Key interfaces:
public interface IAuthority
{
Task<AttestationId> RecordAsync(DsseEnvelope envelope, CancellationToken ct);
Task<Proof> VerifyChainAsync(ArtifactRef artifact, CancellationToken ct);
}
Implementation steps:
- Attestations store
-
Table
attestations:id(AttestationId, PK)artifact_kind/artifact_valuepredicate_type(enum)payload_typepayload_hashenvelope_jsoncreated_atsigner_keyid
-
Table
trust_log:idattestation_idstatus(verified / failed / pending)reasonverified_atverification_data_json(cert chain, Rekor log index, etc.)
- Verification pipeline
-
Implement
IAttestationVerifier.VerifyAsync:- Check envelope integrity (no duplicate signatures, required fields).
- Verify crypto signature (keys from configuration store or Sigstore if you integrate later).
-
IAuthority.RecordAsync:- Verify envelope.
- Save to
attestations. - Add entry to
trust_log.
-
VerifyChainAsync:-
For a given
ArtifactRef:-
Load all attestations for that artifact.
-
Ensure each is
status=verified. -
Compute
InputsLock= hash of:- Sorted predicate payloads.
- Feeds manifest.
- Policy rules.
-
Return
Proof.
-
-
3.2 /attest API
- POST /attest: submit DSSE envelope (for external tools).
- GET /attest?artifact=
...: list attestations + trust status. - GET /attest/{id}/proof: return verification proof (including InputsLock).
Acceptance criteria
- Invalid signatures rejected.
- Tampering test: alter a byte in envelope JSON → verification fails.
VerifyChainAsyncreturns sameProof.InputsLockfor identical sets of inputs.
4. Graph Store & Policy engine (Phase 4)
Goal: Store SBOM, vulnerabilities, reachability, VEX, and query them to make deterministic VEX-gate decisions.
4.1 Graph model (StellaOps.Graph)
Tables (simplified):
-
artifacts:id(PK),kind,value,digest_algorithm,digest_value
-
components:id,purl,name,version,license,digest_algorithm,digest_value
-
vulnerabilities:id,cve_id,severity,source(NVD/OSV/vendor),data_json
-
vex_statements:id,cve_id,component_purl,status(not_affected,affected, etc.),source,data_json
-
edges:id,from_kind,from_id,to_kind,to_id,relation(enum),evidence_id,data_json
Example relation values:
artifact_contains_componentcomponent_vulnerable_tocomponent_reachable_viavulnerability_overridden_by_vexartifact_scanned_bydecision_verified_by
Graph access abstraction:
public interface IGraphRepository
{
Task UpsertSbomAsync(CanonicalSbom sbom, EvidenceId evidenceId, CancellationToken ct);
Task ApplyVulnerabilityFactsAsync(IEnumerable<VulnerabilityFact> facts, CancellationToken ct);
Task ApplyReachabilityFactsAsync(IEnumerable<ReachabilityFact> facts, CancellationToken ct);
Task ApplyVexStatementsAsync(IEnumerable<VexStatement> vexStatements, CancellationToken ct);
Task<ArtifactGraphSnapshot> GetSnapshotAsync(ArtifactRef artifact, CancellationToken ct);
}
ArtifactGraphSnapshot is an in-memory projection used by the policy engine.
4.2 Policy engine (StellaOps.Policy)
Policy lattice (minimal version):
public enum RiskState
{
Clean,
VulnerableNotReachable,
VulnerableReachable,
Unknown
}
public sealed class PolicyEvaluationContext
{
public ArtifactRef Artifact { get; init; } = default!;
public ArtifactGraphSnapshot Snapshot { get; init; } = default!;
public IReadOnlyDictionary<string,string>? Environment { get; init; }
}
public interface IPolicyEngine
{
GateDecision Evaluate(PolicyEvaluationContext context);
}
Default policy logic:
-
For each vulnerability affecting a component in the artifact:
-
Check for VEX:
- If trusted VEX says
not_affected→ ignore.
- If trusted VEX says
-
Check reachability:
- If proven reachable → mark as
VulnerableReachable. - If proven not reachable →
VulnerableNotReachable. - If unknown →
Unknown.
- If proven reachable → mark as
-
-
Aggregate:
- If any
Critical/HighinVulnerableReachable→Block. - Else if any
Critical/HighinUnknown→Warnand log Unknowns. - Else →
Allow.
4.3 /vex-gate endpoint
Implementation:
- Resolve
ArtifactRef. - Build
ArtifactGraphSnapshotusingIGraphRepository.GetSnapshotAsync. - Call
IPolicyEngine.Evaluate. - Request
IAuthority.VerifyChainAsync→Proof. - Emit
PolicyVerifiedattestation for this decision. - Return
GateDecision+Proof.
Acceptance criteria
-
Given a fixture DB snapshot, calling
/vex-gatetwice yields identical decisions & proof IDs. -
Policy behavior matches the rule text:
- Regression test that modifies severity or reachability → correct decision changes.
5. Diffs & Unknowns workflow (Phase 5)
5.1 Diff engine (/diff)
Contracts:
public sealed class DiffRequest
{
public string Kind { get; init; } = default!; // "sbom-sbom" | "sbom-runtime"
public string LeftId { get; init; } = default!;
public string RightId { get; init; } = default!;
}
public sealed class DiffComponentChange
{
public string Purl { get; init; } = default!;
public string ChangeType { get; init; } = default!; // "added" | "removed" | "changed"
public string? OldVersion { get; init; }
public string? NewVersion { get; init; }
}
public sealed class DiffResponse
{
public IReadOnlyList<DiffComponentChange> Components { get; init; } = Array.Empty<DiffComponentChange>();
}
Implementation:
-
SBOM↔SBOM: compare
CanonicalSbom.Componentsby PURL (+ version). -
SBOM↔runtime:
- Input runtime snapshot (
process maps,loaded libs, etc.) from agents. - Map runtime libs to PURLs.
- Determine reachable components from runtime usage →
ReachabilityFacts into graph.
- Input runtime snapshot (
5.2 Unknowns module (/unknowns)
Data model:
public enum UnknownState
{
New,
Triage,
VendorQuery,
Verified,
Closed
}
public sealed class Unknown
{
public Guid Id { get; init; }
public ArtifactRef Artifact { get; init; } = default!;
public string Type { get; init; } = default!; // "vuln-mapping", "reachability", "vex-trust"
public string Subject { get; init; } = default!; // e.g., "CVE-2024-XXXX / purl:pkg:..."
public UnknownState State { get; set; }
public DateTimeOffset CreatedAt { get; init; }
public DateTimeOffset? SlaDeadline { get; set; }
public string? Owner { get; set; }
public string EvidenceJson { get; init; } = default!; // serialized proof / edges
public string? ResolutionNotes { get; set; }
}
API:
GET /unknowns: filter by state, artifact, owner.POST /unknowns: create manual unknown.PATCH /unknowns/{id}: update state, owner, notes.
Integration:
-
Policy engine:
- For any
Unknownrisk state, auto-create Unknown with SLA if not already present.
- For any
-
When Unknown resolves (e.g., vendor VEX added), re-run policy evaluation for affected artifact(s).
Acceptance criteria
-
When
VulnerableReachabilityisUnknown,/vex-gateboth:- Returns
Warn. - Creates an Unknown row.
- Returns
-
Transitioning Unknown to
Verifiedtriggers re-evaluation (integration test).
6. Offline / air‑gapped bundles (Phase 6)
Goal: Everything works on a single machine with no network.
6.1 Bundle format & IO (StellaOps.Cli + StellaOps.WebApi)
Directory structure inside ZIP:
/bundle/
feeds/
manifest.json // hashes, timestamps for NVD, OSV, vendor feeds
nvd.json
osv.json
vendor-*.json
sboms/
{artifactDigest}.json
attestations/
*.jsonl // one DSSE envelope per line
proofs/
rekor/
merkle.json
policy/
lattice.json // serialized rules / thresholds
replay/
inputs.lock // hash & metadata of all of the above
Implement:
public interface IBundleReader
{
Task<Bundle> ReadAsync(string path, CancellationToken ct);
}
public interface IBundleWriter
{
Task WriteAsync(Bundle bundle, string path, CancellationToken ct);
}
Bundle holds strongly-typed representations of the manifest, SBOMs, attestations, proofs, etc.
6.2 CLI commands
-
stella scan --image registry/app:1.2.3 --out bundle.zip-
Runs scan + sbom locally.
-
Writes bundle with:
- SBOM.
- Scan + Sbom attestations.
- Feeds manifest.
-
-
stella vex-gate --bundle bundle.zip- Loads bundle.
- Runs policy engine locally.
- Prints
Allow/Warn/Block+ proof summary.
Acceptance criteria
- Given the same
bundle.zip,stella vex-gateon different machines produces identical decisions and proof hashes. /vex-gate?bundle=/path/to/bundle.zipin API uses same BundleReader and yields same output as CLI.
7. Testing & quality plan
7.1 Unit tests
-
Domain & Contracts:
- Serialization roundtrip for all DTOs.
-
Attest:
- DSSE encode/decode.
- Signature verification with test key pair.
-
Sbom:
- Known
ScanResult→ expected SBOM JSON snapshot.
- Known
-
Policy:
-
Table-driven tests:
- Cases: {severity, reachable, hasVex} → {Allow/Warn/Block}.
-
7.2 Integration tests
-
Scanner:
- Use a tiny test image with known components.
-
Graph + Policy:
-
Seed DB with:
- 1 artifact, 2 components, 1 vuln, 1 VEX, 1 reachability fact.
-
Assert that
/vex-gatereturns expected decision.
-
7.3 E2E scenario
Single test flow:
POST /scan→ EvidenceId.POST /sbom→ SBOM + SbomProduced attestation.- Load dummy vulnerability feed →
ApplyVulnerabilityFactsAsync. POST /vex-gate→ Block (no VEX).- Add VEX statement →
ApplyVexStatementsAsync. POST /vex-gate→ Allow.
Assertions:
- All decisions contain
Proofwith non-emptyInputsLock. InputsLockis identical between runs with unchanged inputs.
8. Concrete backlog (you can paste into Jira)
Epic 1 – Foundations
- Task: Create solution & project skeleton.
- Task: Implement core domain types (
Digest,ArtifactRef,EvidenceId,Proof). - Task: Implement DSSE envelope + JSON serialization.
- Task: Implement basic
IAttestationSignerwith local key pair. - Task: Define
GateDecision&VexGateRequestcontracts.
Epic 2 – Scanner & Sbomer
- Task: Implement
IArtifactScanner+SyftScanner. - Task: Implement
/scanendpoint + attestation. - Task: Implement
ISbomer& canonical SBOM model. - Task: Implement
/sbomendpoint + SbomProduced attestation. - Task: Snapshot tests for SBOM determinism.
Epic 3 – Authority & Trust log
- Task: Design
attestations&trust_logtables (EF Core migrations). - Task: Implement
IAuthority.RecordAsync+VerifyChainAsync. - Task: Implement
/attestendpoints. - Task: Add proof generation (
InputsLockhashing).
Epic 4 – Graph & Policy
- Task: Create graph schema (
artifacts,components,vulnerabilities,edges,vex_statements). - Task: Implement
IGraphRepository.UpsertSbomAsync. - Task: Ingest vulnerability feed (NVD/OSV) into graph facts.
- Task: Implement minimal
IPolicyEnginewith rules. - Task: Implement
/vex-gateendpoint.
Epic 5 – Diff & Unknowns
- Task: Implement SBOM↔SBOM diff logic +
/diff. - Task: Create
unknownstable + API. - Task: Wire policy engine to auto-create Unknowns.
- Task: Add re-evaluation when Unknown state changes.
Epic 6 – Offline bundles & CLI
- Task: Implement
BundleReader/BundleWriter. - Task: Implement
stella scanandstella vex-gate. - Task: Add
?bundle=parameter support in APIs.
If you’d like, I can next:
- Turn this into actual C# interface files (ready to drop into your repo), or
- Produce a JSON OpenAPI sketch for
/scan,/sbom,/attest,/vex-gate,/diff,/unknowns.
