StellaOps Scanner

Layer-by-layer container image analysis that produces deterministic SBOM fragments, diffs, and signed reports.

Scanner is the analysis core of the Stella Ops vulnerability scanner. It decomposes container images layer-by-layer, runs OS, language, and native-ecosystem analyzers, and emits deterministic SBOM fragments, three-way diffs, and DSSE-signed reports for downstream policy evaluation, attestation, and export.

This page is the module map and product overview. For the full route catalog see endpoint-registration-matrix.md; for analyzer behavior and operations see the Related resources section.

Responsibilities

Key components

Integrations & dependencies

Operational notes

Operations

Analyzers & evidence

Design

Benchmarks

Implementation Status

Phase 1 – Control plane & job queue (Complete)

Phase 2 – Analyzer parity & SBOM assembly (In Progress)

Phase 3 – Diff & attestations (In Progress)

Phase 4 – Integrations & exports (Planned)

Phase 5 – Observability & resilience (Planned)

Key Acceptance Criteria

Technical Decisions & Risks

Recent Enhancements (2025-12-12)

Gating Explainability (Quiet-by-Design Triage)

The Scanner WebService exposes gating explainability through the triage APIs to support the “Quiet-by-Design” UX pattern where noise is gated at the source and proof is surfaced with one click.

Gating Reasons

Findings can be hidden by default based on the GatingReason enum (Contracts/GatingContracts.cs). Enum members and their numeric values:

Enum member (value)Description
None (0)Not gated; visible in the default view
Unreachable (1)Vulnerable code not reachable from any application entrypoint
PolicyDismissed (2)Policy rule dismissed/waived/tolerated this finding
Backported (3)Patched via distro backport (version comparison confirms fixed)
VexNotAffected (4)VEX statement declares not_affected with sufficient trust
Superseded (5)Superseded by a newer advisory or CVE
UserMuted (6)Explicitly muted by a user decision

Key DTOs

Source: Contracts/GatingContracts.cs and Contracts/TriageContracts.cs.

VEX Trust Scoring

VEX statements are evaluated against a policy trust threshold (default 0.7GatingReasonService.DefaultPolicyTrustThreshold). The composite trust score is a weighted combination of four factors (VexTrustBreakdownDto):

When a not_affected VEX record scores >= DefaultPolicyTrustThreshold, the finding is gated with GatingReason.VexNotAffected.

Unified Evidence Endpoint

GET /api/v1/triage/findings/{findingId}/evidence (route base api/v1/triage, Controllers/TriageController.cs) returns all evidence tabs in one call. Each tab can be toggled via include* query parameters (includeSbom, includeReachability, includeVex, includeAttestations, includeDeltas, includePolicy, includeReplayCommand; all default true). The response is ETag-cached (Cache-Control: private, max-age=300) and honours If-None-Match for 304 Not Modified. Tabs returned:

The canonical findingId is a bare UUID from GET /api/v1/triage/inbox?artifactDigest=..., specifically a value returned in paths[].findingIds[]. The artifact digest is a tenant-scoped multi-row filter; it is not a finding identity. Findings-service matcher tokens are a separate identity namespace and must not be used on Scanner Triage routes.

Real responses include the linked stored scan record and stored finding lifecycle timestamps. A real finding with missing child evidence returns 200 with availability.status=partial and sorted availability.missing[] entries that name the stable absence code, producer, and recovery action. Scanner does not manufacture missing hashes, SBOM/policy versions, attestation verification, or replay inputs: unknown values remain null or unknown. Equal-timestamp child records are selected deterministically using the record UUID as the tie-break. A missing or cross-tenant finding returns a non-echoing typed 404 with producer and inbox-recovery metadata.

Evidence Bundle Export

GET /api/v1/triage/findings/{findingId}/evidence/export?format=zip (also accepts format=tar.gz) returns a downloadable archive built by EvidenceBundleExporter. The response carries an X-Archive-Digest: sha256:... header for verification. A single-finding bundle contains (file names emitted by EvidenceBundleExporter.PrepareEvidenceFilesAsync):

The run-level export (ExportRunAsync) instead nests each finding under findings/<findingId>/ beneath a top-level MANIFEST.json and README.md.

Replay Command Generation

The IReplayCommandService (ReplayCommandService) generates copy-ready CLI commands. The default binary is stellaops (overridable via scanner:replayCommands:binary). The full command form is:

stellaops replay --target <component-purl> --cve <CVE-ID> --feed-snapshot <hash> --policy-hash <hash> --verify

When a knowledge snapshot is available, a short form is also emitted (stellaops replay --target ... --cve ... --snapshot <snapshotId> --verify). For offline replay (IncludeOffline): stellaops replay --target ... --cve ... --bundle ./evidence-<scanId>-<findingId>.tar.gz --offline --verify.

The response (ReplayCommandResponseDto) also carries the ExpectedVerdictHash so a replayed verdict can be byte-compared against the recorded one.

UI Wireframes

Gated Buckets Summary

┌─────────────────────────────────────────────────────────────────────────────┐
│                         Gated Findings Summary                               │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌────────────────────────┐                                                  │
│  │  12  actionable        │  (96 hidden)                                    │
│  └────────────────────────┘                                                  │
│                                                                              │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐        │
│  │ +42          │ │ +15          │ │ +8           │ │ +23          │        │
│  │ unreachable  │ │ policy       │ │ backported   │ │ VEX          │        │
│  └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘        │
│                                                                              │
│  ┌──────────────┐ ┌──────────────┐  ┌─────────────────┐                     │
│  │ +3           │ │ +5           │  │ [Show all]      │                     │
│  │ superseded   │ │ muted        │  └─────────────────┘                     │
│  └──────────────┘ └──────────────┘                                          │
└─────────────────────────────────────────────────────────────────────────────┘

VEX Trust Display

Illustrative UI mock-up. The default policy trust threshold is 0.7 (GatingReasonService.DefaultPolicyTrustThreshold), not 0.80, and the canonical breakdown components are Issuer / Recency / Justification / Evidence (VexTrustBreakdownDto) — the “Authority / Accuracy / Timeliness / Verification” labels below are illustrative only. See “VEX Trust Scoring” above for the authoritative values.

┌─────────────────────────────────────────────────────────────────────────────┐
│  VEX Status: not_affected                                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Trust Score: ████████░░  0.85                                              │
│  Threshold:   ────────    0.80  ✓ Meets policy                              │
│                                                                              │
│  Issuer: vendor.example                                                      │
│  Justification: vulnerable_code_not_in_execute_path                         │
│                                                                              │
│  ┌─ Trust Breakdown ─────────────────────────────────────────────────┐      │
│  │  Authority:    ██████████░  0.90                                  │      │
│  │  Accuracy:     ████████░░░  0.85                                  │      │
│  │  Timeliness:   ████████░░░  0.80                                  │      │
│  │  Verification: ████████░░░  0.85                                  │      │
│  └───────────────────────────────────────────────────────────────────┘      │
└─────────────────────────────────────────────────────────────────────────────┘

Replay Command Component

┌─────────────────────────────────────────────────────────────────────────────┐
│  Replay Command                                                              │
│  Reproduce this verdict deterministically                                    │
├──────────┬─────────┬─────────────────────────────────────────────────────────┤
│ [Full]   │  Short  │  Offline                                               │
├──────────┴─────────┴─────────────────────────────────────────────────────────┤
│  ┌───────────────────────────────────────────────────────────────────────┐  │
│  │ $ stellaops replay \                                                  │  │
│  │     --target pkg:npm/lodash@4.17.15 \                                 │  │
│  │     --cve CVE-2024-1234 \                                             │  │
│  │     --feed-snapshot sha256:feed789... \                              │  │
│  │     --policy-hash sha256:policy321... \                              │  │
│  │     --verify                                                          │  │
│  └───────────────────────────────────────────────────────────────────────┘  │
│                                                           ┌───────────────┐  │
│                                                           │  📋 Copy      │  │
│                                                           └───────────────┘  │
├─────────────────────────────────────────────────────────────────────────────┤
│  📦 Download Evidence Bundle    12.5 KB · ZIP                               │
├─────────────────────────────────────────────────────────────────────────────┤
│  Expected verdict hash: sha256:verdict123...                                │
└─────────────────────────────────────────────────────────────────────────────┘

Gating Explainer Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│                         Finding: CVE-2024-1234                               │
│                         lodash@4.17.15                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Status: Hidden by default   [Why hidden?] ←─┐                              │
│                                              │                              │
│                              ┌───────────────┴───────────────────────────┐  │
│                              │  Why is this finding hidden?              │  │
│                              ├───────────────────────────────────────────┤  │
│                              │                                           │  │
│                              │  This finding is gated because:           │  │
│                              │                                           │  │
│                              │  ✓ VEX not_affected (trust: 0.85)         │  │
│                              │    Vendor issued not_affected statement   │  │
│                              │    with justification:                    │  │
│                              │    "vulnerable_code_not_in_execute_path"  │  │
│                              │                                           │  │
│                              │  Evidence:                                │  │
│                              │  • VEX document: vex-vendor-2025-001      │  │
│                              │  • Issued: 2025-12-15T10:00:00Z           │  │
│                              │  • Signature: ✓ Valid (ES256)             │  │
│                              │                                           │  │
│                              │        [View Evidence] [Close]            │  │
│                              └───────────────────────────────────────────┘  │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Evidence Bundle Contents

Illustrative UI mock-up. The authoritative single-finding file list is the one emitted by EvidenceBundleExporter.PrepareEvidenceFilesAsync (see “Evidence Bundle Export” above): manifest.json, sbom.cdx.json, reachability.json, vex/<source>.json, attestations/<predicate>.dsse.json, policy/, replay.sh, replay.ps1. README.md and the top-level MANIFEST.json are emitted only by the run-level export. Per-VEX-source file names below (nvd.json, cisa-kev.json) are examples, not fixed names.

evidence-f-abc123/
├── manifest.json         ← Archive manifest with SHA-256 hashes
├── README.md             ← Human-readable documentation
├── sbom.cdx.json         ← CycloneDX SBOM slice
├── reachability.json     ← Reachability analysis
├── vex/
│   ├── vendor.json       ← Vendor VEX statement
│   ├── nvd.json          ← NVD data
│   └── cisa-kev.json     ← CISA KEV flag
├── attestations/
│   ├── sbom.dsse.json    ← SBOM DSSE envelope
│   └── scan.dsse.json    ← Scan DSSE envelope
├── policy/
│   └── evaluation.json   ← Policy evaluation result
├── delta.json            ← Delta comparison
├── replay-command.txt    ← Copy-ready CLI command
├── replay.sh             ← Bash replay script
└── replay.ps1            ← PowerShell replay script

See Sprint 9200.0001.0001-0004 for implementation details.

Epic alignment