Files
git.stella-ops.org/docs/product-advisories/14-Dec-2025 - Reachability Analysis Technical Reference.md
2025-12-14 21:29:44 +02:00

27 KiB
Raw Permalink Blame History

Reachability Analysis Technical Reference

Source Advisories:

  • 05-Dec-2025 - Building a Deterministic, ReachabilityFirst Architecture
  • 13-Dec-2025 - Designing the CallStack Reachability Engine
  • 03-Dec-2025 - Reachability Benchmarks and Moat Metrics
  • 09-Dec-2025 - Caching Reachability the Smart Way
  • 06-Dec-2025 - Reachability Methods Worth Testing This Week
  • 04-Dec-2025 - Ranking Unknowns in Reachability Graphs
  • 02-Dec-2025 - Designing Deterministic Reachability UX
  • 05-Dec-2025 - Design Notes on SmartDiff and CallStack Analysis

Last Updated: 2025-12-14


1. ARCHITECTURE PATTERNS

1.1 Core System Architecture

Module Responsibilities

  • StellaOps.Scanner.WebService: Authoritative for reachability pipeline and lattice computation
  • Language workers: Stateless compute producing CallGraph.v1.json
  • Runtime collectors: Agent/sidecar emitting evidence events only
  • Concelier/Excititor: Provide pruned sources; never compute reachability
  • Authority: Manages replay manifests, crypto profiles
  • Scheduler: Executes rescan/escalation policies

Architectural Rules

  • Scanner = origin of truth for reachability
  • Concelier/Vexer = prune-preservers only
  • Authority = replay manifest owner
  • Scheduler = executor of policies
  • Postgres = System of Record (SoR)
  • Valkey = ephemeral only (dedupe, hot cache, rate limits)

1.2 Evidence Graph Structure

Node Types

  • Artifact, Component, Vulnerability, Attestation, Build, Deployment, RuntimeSignal

Edge Types

  • DESCRIBES, AFFECTS, NOT_AFFECTED_BY, FIXED_IN, DERIVED_FROM, DEPLOYS, OBSERVED_AT_RUNTIME

Edge Signing

  • Sign edges, not just nodes (edge = claim)

1.3 Determinism Requirements

Input Manifest Structure

{
  "scannerVersion": "1.3.0",
  "rulesetId": "stella-default-2025.11",
  "feeds": {
    "nvdDigest": "sha256:...",
    "osvDigest": "sha256:..."
  },
  "sbomDigest": "sha256:...",
  "policyDigest": "sha256:..."
}

Canonicalization Rules

  • Sort arrays by stable keys
  • Normalize paths (POSIX style)
  • Line endings (LF)
  • Encodings (UTF-8)
  • No environment variables in core algorithms
  • No machine-local files
  • No system clock inside algorithms

1.4 Build Once, Query Many (avoid all-pairs precompute)

Separate graph construction from reachability queries:

  1. Build step (once per artifact/version)

    • Produce a deterministic call graph index: CallGraph.v1.json (or a binary format with an accompanying canonical JSON manifest and digest).
    • Persist it content-addressed and bind it into the ReplayManifest.
  2. Query step (per entrypoint/sink query)

    • Run a bounded search (BFS / bidirectional BFS / A*) over the stored index.
    • Return: reachable, a canonical shortest path, and why[] evidence (callsite/method IDs).

Rule: never compute full transitive-closure tables unless the graph is proven small and bounded; prefer per-query search + caching keyed by immutable inputs.

1.5 Deterministic Reachability Cache Key

Cache query results, not graph construction:

reachabilityCacheKey = (
  graphRevisionId,
  algorithmId,
  fromSymbolId,
  toSymbolId,
  contextHash        // entrypoints, flags/env, runtime profile selector
)

Cache requirements:

  • Include the canonical path in cache entries; tie-break with stable neighbor ordering.
  • Cache negative results explicitly (reachable=false) and represent uncertainty (reachable=null) without coercion.
  • Invalidate by construction: when graphRevisionId changes, cache keys naturally change.

1.6 Compositional Library Summaries (ReachCheck-style option)

For third-party libraries, evaluate a compositional approach:

  • Precompute library reachability summaries once per (purl, version, digest) offline.
  • Merge summaries with the application graph at query time.
  • Store each summary as content-addressed evidence with its own digest and tool version.

If using matrix-based transitive-closure summaries, treat them as an optimization layer only: surface any unsoundness/unknowns explicitly and keep all outputs deterministic (no sampling, no nondeterministic parallel traversal ordering).

2. DATA CONTRACTS

2.1 CallGraph.v1.json Schema

{
  "schema": "stella.callgraph.v1",
  "scanKey": "uuid",
  "language": "dotnet|java|node|python|go|rust|binary",
  "artifacts": [{
    "artifactKey": "<artifactKey>",
    "kind": "assembly|jar|module|binary",
    "sha256": "<sha256>"
  }],
  "nodes": [{
    "nodeId": "<nodeId>",
    "artifactKey": "<artifactKey>",
    "symbolKey": "Namespace.Type::Method(<signature>)",
    "visibility": "public|internal|private|unknown",
    "isEntrypointCandidate": false
  }],
  "edges": [{
    "from": "nodeId",
    "to": "nodeId",
    "kind": "static|heuristic",
    "reason": "direct_call|virtual_call|reflection_string|di_binding|dynamic_import|unknown",
    "weight": 1.0
  }],
  "entrypoints": [{
    "nodeId": "<nodeId>",
    "kind": "http|grpc|cli|job|event|unknown",
    "route": "/api/orders/{id}",
    "framework": "aspnetcore|minimalapi|spring|express|unknown"
  }]
}

2.2 RuntimeEvidence.v1.json Schema

{
  "schema": "stella.runtimeevidence.v1",
  "scanKey": "uuid",
  "collectedAt": "2025-12-14T10:00:00Z",
  "environment": {
    "os": "linux|windows",
    "k8s": {"namespace": "<namespace>", "pod": "<pod>", "container": "<container>"},
    "imageDigest": "sha256:<digest>",
    "buildId": "<buildId>"
  },
  "samples": [{
    "timestamp": "<utc-iso8601>",
    "pid": 1234,
    "threadId": 77,
    "frames": ["nodeId","nodeId","nodeId"],
    "sampleWeight": 1.0
  }],
  "loadedArtifacts": [{
    "artifactKey": "<artifactKey>",
    "evidence": "loaded_module|mapped_file|jar_loaded"
  }]
}

2.3 ReplayManifest.json Schema

{
  "schema": "stella.replaymanifest.v1",
  "scanId": "uuid",
  "inputs": {
    "sbomDigest": "sha256:<digest>",
    "callGraphs": [{"language":"dotnet","digest":"sha256:<digest>"}],
    "runtimeEvidence": [{"digest":"sha256:<digest>"}],
    "concelierSnapshot": "sha256:<digest>",
    "excititorSnapshot": "sha256:<digest>",
    "policyDigest": "sha256:<digest>"
  }
}

2.4 ProofSpine Data Model

public sealed record ProofSpine(
    string SpineId,
    string ArtifactId,
    string VulnerabilityId,
    string PolicyProfileId,
    IReadOnlyList<ProofSegment> Segments,
    string Verdict,
    string VerdictReason,
    string RootHash,
    string ScanRunId,
    DateTimeOffset CreatedAt,
    string? SupersededBySpineId
);

public sealed record ProofSegment(
    string SegmentId,
    string SegmentType,
    int Index,
    string InputHash,
    string ResultHash,
    string? PrevSegmentHash,
    DsseEnvelope Envelope,
    string ToolId,
    string ToolVersion,
    string Status
);

Segment Types

  • SBOM_SLICE: Component relevance
  • MATCH: SBOM-to-vuln mapping
  • REACHABILITY: Symbol reachability
  • GUARD_ANALYSIS: Config/feature flag gates
  • RUNTIME_OBSERVATION: Runtime evidence
  • POLICY_EVAL: Lattice decision

3. DATABASE SCHEMAS

3.1 Core Reachability Tables (Postgres)

-- Scan tracking
CREATE TABLE scan (
    scan_id uuid PRIMARY KEY,
    created_at timestamptz,
    repo_uri text,
    commit_sha text,
    sbom_digest text,
    policy_digest text,
    status text
);
CREATE INDEX idx_scan_cache ON scan(commit_sha, sbom_digest);

-- Artifacts
CREATE TABLE artifact (
    artifact_id uuid PRIMARY KEY,
    scan_id uuid REFERENCES scan,
    artifact_key text,
    kind text,
    sha256 text,
    build_id text,
    purl text,
    UNIQUE(scan_id, artifact_key)
);

-- Call graph nodes
CREATE TABLE cg_node (
    scan_id uuid,
    node_id text,
    artifact_key text,
    symbol_key text,
    visibility text,
    flags int,
    PRIMARY KEY(scan_id, node_id)
);

-- Call graph edges
CREATE TABLE cg_edge (
    scan_id uuid,
    from_node_id text,
    to_node_id text,
    kind smallint,
    reason smallint,
    weight real,
    PRIMARY KEY(scan_id, from_node_id, to_node_id, kind, reason)
);
CREATE INDEX idx_cg_edge_from ON cg_edge(scan_id, from_node_id);
CREATE INDEX idx_cg_edge_to ON cg_edge(scan_id, to_node_id);

-- Entrypoints
CREATE TABLE entrypoint (
    scan_id uuid,
    node_id text,
    kind text,
    framework text,
    route text,
    PRIMARY KEY(scan_id, node_id, kind, framework, route)
);

-- Runtime samples
CREATE TABLE runtime_sample (
    scan_id uuid,
    collected_at timestamptz,
    env_hash text,
    sample_id bigserial PRIMARY KEY,
    timestamp timestamptz,
    pid int,
    thread_id int,
    frames text[],
    weight real
);

-- Symbol-to-component mapping
CREATE TABLE symbol_component_map (
    scan_id uuid,
    node_id text,
    purl text,
    mapping_kind text,
    confidence real,
    PRIMARY KEY(scan_id, node_id, purl)
);

-- Reachability results
CREATE TABLE reachability_component (
    scan_id uuid,
    purl text,
    status smallint,
    confidence real,
    why jsonb,
    evidence jsonb,
    PRIMARY KEY(scan_id, purl)
);

CREATE TABLE reachability_finding (
    scan_id uuid,
    cve_id text,
    purl text,
    status smallint,
    confidence real,
    why jsonb,
    evidence jsonb,
    PRIMARY KEY(scan_id, cve_id, purl)
);

3.2 Unknowns Ranking Tables

CREATE TABLE unknowns (
    unknown_id uuid PRIMARY KEY,
    pkg_id text,
    pkg_version text,
    digest_anchor bytea,
    unknown_flags jsonb,
    popularity_p float,
    potential_e float,
    uncertainty_u float,
    centrality_c float,
    staleness_s float,
    score float,
    band text CHECK(band IN ('HOT','WARM','COLD')),
    graph_slice_hash bytea,
    evidence_set_hash bytea,
    normalization_trace jsonb,
    callgraph_attempt_hash bytea,
    created_at timestamptz,
    updated_at timestamptz
);

CREATE TABLE deploy_refs (
    pkg_id text,
    image_id text,
    env text,
    first_seen timestamptz,
    last_seen timestamptz
);

CREATE TABLE graph_metrics (
    pkg_id text PRIMARY KEY,
    degree_c float,
    betweenness_c float,
    last_calc_at timestamptz
);

3.3 Proof Spine Tables

CREATE TABLE proof_spines (
    spine_id uuid PRIMARY KEY,
    artifact_id text,
    vuln_id text,
    policy_profile_id text,
    verdict text,
    verdict_reason text,
    root_hash text,
    scan_run_id uuid,
    created_at timestamptz,
    superseded_by_spine_id uuid,
    segment_count int
);
CREATE INDEX idx_spine_lookup ON proof_spines(artifact_id, vuln_id, policy_profile_id);

CREATE TABLE proof_segments (
    segment_id uuid PRIMARY KEY,
    spine_id uuid REFERENCES proof_spines,
    idx int,
    segment_type text,
    input_hash text,
    result_hash text,
    prev_segment_hash text,
    envelope bytea,
    tool_id text,
    tool_version text,
    status text,
    created_at timestamptz
);

4. ALGORITHMS

4.1 Reachability Status Classification

Status Values

  • UNREACHABLE: No path from entrypoints
  • POSSIBLY_REACHABLE: Graph incomplete/dynamic behavior
  • REACHABLE_STATIC: Static path exists
  • REACHABLE_PROVEN: Runtime evidence confirms

Confidence Scoring (Deterministic)

Base scores:

UNREACHABLE → 0.05
POSSIBLY_REACHABLE → 0.35
REACHABLE_STATIC → 0.70
REACHABLE_PROVEN → 0.95

Modifiers:

+0.10 if path uses only static edges
-0.15 if path includes reflection_string|dynamic_import
+0.10 if runtime evidence hits affected component
-0.10 if NO_ENTRYPOINTS_DISCOVERED
Clamp to [0, 1]

4.2 Reachability Computation Algorithm

Inputs:
- Call graph nodes/edges + entrypoints
- Runtime evidence (optional)
- SBOM with purls
- Vulnerability facts (CVE ↔ purl/version)
- VEX statements

Steps:
1. Build adjacency list for cg_edge.kind in (static, heuristic)
2. Optional: Compress SCCs (Tarjan/Kosaraju)
3. Seed from entrypoints; if empty → mark POSSIBLY_REACHABLE with NO_ENTRYPOINTS_DISCOVERED
4. Traverse reachable nodes; track:
   - firstSeenFromEntrypoint[node]
   - pathWitness[node]
5. Map reachable nodes to purls via symbol_component_map:
   Priority order:
   a. Exact binary symbol → package metadata
   b. Assembly/jar/module to SBOM component (hash/purl)
   c. Heuristics: namespace prefixes, import paths, jar manifest
6. Runtime evidence upgrade:
   - Mark frame nodes as "executed"
   - Mint runtime edges: consecutive frames → runtime_minted
   - Upgrade to REACHABLE_PROVEN if executed node maps to affected purl
7. Compute confidence using deterministic formula

4.3 Unknowns Ranking Algorithm

Score Formula

Score = clamp01(
    wP·P  +  # Popularity impact
    wE·E  +  # Exploit consequence potential
    wU·U  +  # Uncertainty density
    wC·C  +  # Graph centrality
    wS·S     # Evidence staleness
)

Default Weights

wP = 0.25  (deployment impact)
wE = 0.25  (potential consequence)
wU = 0.25  (uncertainty density)
wC = 0.15  (graph centrality)
wS = 0.10  (evidence staleness)

Heuristics

P = min(1, log10(1 + deployments)/log10(1 + 100))
U = sum of flags, capped at 1.0:
    +0.30 if no provenance anchor
    +0.25 if version_range
    +0.20 if conflicting_feeds
    +0.15 if missing_vector
    +0.10 if unreachable source advisory
S = min(1, age_days / 14)

Band Assignment

Score ≥ 0.70 → HOT (immediate rescan + VEX escalation)
0.40 ≤ Score < 0.70 → WARM (scheduled rescan 12-72h)
Score < 0.40 → COLD (weekly batch)

4.4 Node ID Computation (.NET)

Primary (IL-based)

nodeId = SHA256(MVID + ":" + metadataToken + ":" + arity + ":" + signatureShape)

Fallback (source-only)

nodeId = SHA256(projectPath + ":" + file + ":" + span + ":" + symbolDisplayString)

External nodes

nodeId = SHA256("ext:" + artifactKey + ":" + symbolKey)

4.5 Canonical Symbol Key Format

{Namespace}.{TypeName}[`Arity][+Nested]::{MethodName}[`Arity]({ParamType1},{ParamType2},...)

Rules:

  • Use System.* full names for BCL types
  • Use + for nested types (metadata style)
  • Use backtick arity for generic type/method definitions
  • Arrays: System.String[]
  • Byref: System.String&

4.6 Reachability Cache Algorithm

public readonly record struct ReachKey(
    string AlgoSig,        // e.g., "RTA@sha256:…"
    string InputsHash,     // SBOM slice + policy + versions
    string CallPathHash    // normalized query graph
);

public sealed class ReachCache {
    private readonly ConcurrentDictionary<ReachKey, Lazy<Task<ReachResult>>> _memo = new();

    public Task<ReachResult> GetOrComputeAsync(
        ReachKey key,
        Func<Task<ReachResult>> compute,
        CancellationToken ct)
    {
        var lazy = _memo.GetOrAdd(key, _ => new Lazy<Task<ReachResult>>(
            () => compute(), LazyThreadSafetyMode.ExecutionAndPublication));

        return lazy.Value.ContinueWith(t => {
            if (t.IsCompletedSuccessfully) return t.Result;
            _memo.TryRemove(key, out _);
            throw t.Exception ?? new Exception("reachability failed");
        }, ct);
    }
}

Operational rules

  • Canonical everything: sort nodes/edges, normalize paths
  • Cache scope: per-scan, per-workspace, or per-feed version
  • TTL: 15-60 minutes or evict after pipeline completes
  • Max-entries cap
  • Concurrency: Lazy<Task<>> coalesces duplicate in-flight calls

4.7 Proof Spine Construction

1. Sbomer produces SBOM_SLICE segment, DSSE-signs it
2. Scanner produces MATCH segment
3. Reachability produces REACHABILITY segment
4. Guard Analyzer produces GUARD_ANALYSIS segment
5. Vexer evaluates lattice, produces POLICY_EVAL segment
6. ProofSpineBuilder:
   - Sorts segments by predetermined order
   - Chains PrevSegmentHash
   - Computes RootHash = hash(concat of segment hashes)
   - Stores ProofSpine with deterministic SpineId

5. API CONTRACTS

5.1 Scanner Ingestion Endpoints

POST /api/scans
  Returns: scanId

POST /api/scans/{scanId}/callgraphs
  Body: CallGraph.v1.json
  Header: Content-Digest (for idempotency)

POST /api/scans/{scanId}/runtimeevidence
  Body: RuntimeEvidence.v1.json

POST /api/scans/{scanId}/sbom
  Body: CycloneDX/SPDX

POST /api/scans/{scanId}/compute-reachability
  Idempotent trigger

5.2 Query Endpoints

GET /api/scans/{scanId}/reachability/components?purl=...
GET /api/scans/{scanId}/reachability/findings?cve=...
GET /api/scans/{scanId}/reachability/explain?cve=...&purl=...
  Returns: why[], path witness, sample refs

5.3 Export Endpoints

GET /api/scans/{scanId}/exports/sarif
GET /api/scans/{scanId}/exports/cdxr  # CycloneDX reachability extension
GET /api/scans/{scanId}/exports/openvex

5.4 Proof Spine Endpoints

GET /graph/runtime/{podId}/lineage
GET /graph/image/{digest}/vex
GET /spines/{spineId}
  Returns: ProofSpine with all segments

6. .NET IMPLEMENTATION PATTERNS

6.1 .NET Worker (Roslyn + IL) Required Features

Call Graph Extraction

  • Direct invocation edges: InvocationExpressionSyntax
  • Object creation edges: constructors
  • Delegate invocation: record heuristic edge when target unresolved
  • Virtual/interface dispatch: record virtual_call edge to declared method
  • Async/await: connect logical caller → awaited method

Entrypoint Discovery

  • Program.Main (classic)
  • ASP.NET Core:
    • Controllers: [ApiController], route attributes, action methods
    • Minimal APIs: MapGet/MapPost/MapMethods patterns
    • gRPC: MapGrpcService<T>() and service methods
    • Hosted services: IHostedService, BackgroundService.ExecuteAsync

Reflection and DI Heuristics

  • Type.GetType("…"), Assembly.GetType, GetMethod("…"), Invoke
  • services.AddTransient<IFoo,Foo>() / AddScoped / AddSingleton
  • Activator.CreateInstance, ServiceProvider.GetService
  • Produce heuristic edges with reason = di_binding or reflection_string

6.2 NuGet Dependencies

<!-- Roslyn / MSBuild -->
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="5.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.0.0" />
<PackageReference Include="Microsoft.Build.Locator" Version="*" />

<!-- IL + metadata -->
<!-- Use System.Reflection.Metadata (BCL) -->
<!-- Optional: Mono.Cecil for faster IL traversal -->

<!-- CLI + JSON -->
<PackageReference Include="System.CommandLine" Version="*" />
<PackageReference Include="System.Text.Json" Version="*" />

6.3 Roslyn IL Opcodes for Call Detection

Recognize as "calls":
- call
- callvirt
- newobj
- jmp
- ldftn
- ldvirtftn

6.4 MSBuild Workspace Loading Pattern

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;

var ws = MSBuildWorkspace.Create();
var sln = await ws.OpenSolutionAsync(@"path\to.sln");
foreach (var proj in sln.Projects)
foreach (var doc in proj.Documents)
{
    var model = await doc.GetSemanticModelAsync();
    var root = await doc.GetSyntaxRootAsync();
    foreach (var node in root.DescendantNodes()
        .OfType<InvocationExpressionSyntax>())
    {
        var sym = model.GetSymbolInfo(node).Symbol as IMethodSymbol;
        if (sym != null)
        {
            // record edge: caller -> sym
        }
    }
}

7. CONFIGURATION PATTERNS

7.1 Crypto Profiles

public interface ICryptoProfile
{
    string ProfileId { get; }
    byte[] Sign(byte[] data, string keyId);
    bool Verify(byte[] data, byte[] signature, string keyId);
}

// Implementations: FipsProfile, GostProfile, EidasProfile, SmProfile, PqcProfile

Profile Metadata

  • Algorithm
  • Key ID
  • Profile name

Key Rotation

  • Keys have validity intervals
  • Spines keep KeyId in each DSSE signature
  • Authority maintains trust table: which keys trusted for which SegmentType and time window

7.2 Offline Bundle Format

Required Contents

  • SBOM + feeds + policy bundle + key material
  • Manifest with hashes of all contents
  • Replay manifest for deterministic rerun

Format

  • Zip/tar + manifest
  • Each entry content-addressed

7.3 Cache Configuration (appsettings)

{
  "Scanner": {
    "Reach": {
      "Cache": {
        "MaxEntries": 10000,
        "TtlMinutes": 60,
        "EvictOnPipelineComplete": true
      }
    }
  }
}

8. METRICS AND THRESHOLDS

8.1 Performance SLOs (v1)

Medium service (100k LOC .NET) static graph: < 2 minutes
Reachability compute: < 30 seconds
Query GET finding: < 200ms p95

8.2 Quality Metrics

Proof verification

  • % verified proofs
  • Proof verification failures
  • Proof age since last verification
  • % entries with valid inclusion proof (Rekor)

Unknowns triage

  • Hot/Warm/Cold distribution
  • Rescan success rate after N attempts
  • Evidence staleness distribution

Drift detection

  • Runtime edges not in static graph (above threshold → emit COVERAGE_DRIFT warning)

8.3 Benchmark Metrics (Moat)

Metric Target
Time-to-evidence: SBOM → signed call-graph < 5 minutes for 100k LOC service
SBOM-diff false positive rate under dependency churn < 5% change in reachability status for non-code changes
Deterministic priority scoring under air-gap replay Bit-identical results given same inputs
Proof verification time < 200ms p95

9. TESTING PATTERNS

9.1 Golden Corpus Requirements

Mandatory Test Cases

  1. Minimal ASP.NET controller with reachable endpoint → vulnerable lib call
  2. Vulnerable lib present but never called → unreachable
  3. Reflection-based activation → "possible" unless runtime proves
  4. BackgroundService job case
  5. Version range ambiguity
  6. Mismatched epoch/backport
  7. Missing CVSS vector
  8. Conflicting severity vendor/NVD
  9. Unanchored filesystem library

Assertions per Test

  • Reachability status
  • At least one why[] reason
  • Deterministic confidence within ±0.01
  • Segments with expected status (verified/partial/invalid)

9.2 Replay Manifest Tests

Given manifest containing:

  • feed hashes
  • rules version
  • normalization logic
  • lattice rules

Assert: ranking/reachability recomputes identically (byte-for-byte)

9.3 Signature Tampering Tests

  • Flip byte in DSSE payload → UI must show invalid
  • Mark key untrusted → segments show untrusted-key
  • Entire spine marked as compromised

10. SPECIFICATION COMPLIANCE

10.1 SBOM Standards

CycloneDX

  • Version: 1.6 (ECMA-424) and 1.7 (current)
  • Media type: include version parameter
  • Ingest: validate against ECMA-424/1.7 schemas

SPDX

  • Version: 3.0.1
  • Validate against canonical spec

Ingestion Rules

  • Accept only *.cdx.json and *.spdx.json
  • Hard fail others
  • Store: raw bytes + parsed form + normalized canonical form

10.2 VEX Standards

OpenVEX

  • Minimal, SBOM-agnostic
  • Predicate type: https://openvex.dev/ns/v0.2.0

CSAF VEX

  • Alternative format for interoperability

Required VEX Fields

  • status: not_affected|affected|fixed|under_investigation
  • justification
  • timestamp
  • author
  • link to supporting evidence

10.3 Attestation Standards

DSSE (Dead Simple Signing Envelope)

  • Use for all signed artifacts
  • Payload: canonical JSON
  • Envelope: signature + key metadata + profile

in-toto

  • Statement structure
  • Predicate types:
    • SBOM: https://spdx.dev/Document
    • VEX: OpenVEX predicate URI
    • Custom: reachability predicates

Cosign Integration

cosign attest --yes --type https://spdx.dev/Document \
  --predicate sbom.spdx.json \
  --key cosign.key \
  "${IMAGE_DIGEST}"

10.4 Rekor Integration

CLI Verification

rekor-cli verify --rekor_server https://rekor.sigstore.dev \
  --signature artifact.sig \
  --public-key cosign.pub \
  --artifact artifact.bin

Persistence per Entry

  • Rekor UUID
  • Log index
  • Integrated time
  • Inclusion proof data

11. CLI COMMANDS

11.1 Worker CLI

# Artifacts-first scan
stella-worker-dotnet scan \
  --scanKey 00000000-0000-0000-0000-000000000000 \
  --assemblies ./artifacts/bin/Release \
  --out ./callgraph.json

# Build-and-scan
stella-worker-dotnet scan \
  --scanKey ... \
  --sln ./src/MySolution.sln \
  --configuration Release \
  --tfm net10.0 \
  --buildMode build \
  --out ./callgraph.json

# Upload to scanner.webservice
stella-worker-dotnet scan \
  --scanKey ... \
  --assemblies ./artifacts/bin/Release \
  --upload https://scanner/api/scans/{scanId}/callgraphs \
  --apiKey $STELLA_API_KEY

11.2 Replay CLI

stellaops scan \
  --replay-manifest <id-or-file> \
  --artifact <image-digest> \
  --vuln <cve> \
  --explain

11.3 Reachability Query CLI

stella scan graph --lang dotnet --sln path.sln --out graph.scc.json
stella scan runtime --target pod/myservice --duration 30s --out stacks.json
stella reachability join \
  --graph graph.scc.json \
  --runtime stacks.json \
  --sbom bom.cdx.json \
  --out reach.cdxr.json

12. DEVELOPER CHECKLIST

12.1 Per-Feature Definition of Done

For any feature touching scans, VEX, or evidence:

  • Deterministic: input manifest defined, canonicalization applied, golden fixture(s) added
  • Evidence: outputs DSSE-wrapped and linked
  • Reachability/Lattice: runs only in allowed services, records algorithm IDs
  • Crypto: calls through profile abstraction, tests for ≥2 profiles
  • Graph: lineage edges added, node/edge IDs stable and queryable
  • UX/API: API to retrieve structured evidence
  • Tests: unit + golden + integration test with full SBOM → scan → VEX chain

12.2 Determinism Checklist

  • Input manifest type defined and versioned
  • Canonicalization applied before hashing/signing
  • Output stored with inputsDigest and algoDigest
  • At least one golden fixture proves determinism
  • No environment variables in core algorithm
  • No machine-local files
  • No system clock inside algorithms

12.3 Reachability Implementation Checklist

  • Reachability algorithms only in Scanner.WebService
  • Cache lazy and keyed by deterministic inputs
  • Output includes explicit evidence pointers
  • UI endpoints expose reachability state in structured form
  • All modifiers recorded in why[]

12.4 Crypto-Sovereign Checklist

  • No direct crypto calls in feature code; only via profile layer
  • All attestations carry algorithm + key id + profile
  • Offline bundle type exists for this workflow
  • Tests for at least 2 different crypto profiles

12.5 Policy/Lattice Checklist

  • Facts and policies serialized separately
  • Lattice code in allowed services only
  • Merge strategies named and versioned
  • Artifacts record which lattice algorithm used

12.6 Proof-Linked VEX Checklist

  • VEX schema includes pointers to all upstream artifacts (sbomDigest, scanId, reachMapDigest, policyDigest, signerKeyId)
  • No duplication of SBOM/scan content inside VEX
  • DSSE used as standard envelope type

12.7 Unknowns Triage Checklist

  • Persist all traces for deterministic replay
  • Ranking depends only on manifest-declared parameters
  • All uncertainty factors are explicit flags
  • Scoring reproducible under identical inputs
  • Scheduler decision table deterministic and tested
  • API exposes full reasoning

Document Version: 1.0 Target Platform: .NET 10, PostgreSQL ≥16, Angular v17