Stella-vs-Trivy benchmark enhancement — consolidated findings

Status: research capture (pre-plan). Date: 2026-06-21. Purpose: single durable reference consolidating four investigations, so the implementation sprint is built on complete knowledge. No code changed yet. Source workflows (full transcripts under the session subagents dir): gap analysis w99iukh19, scoring recon w9km5w72n, oracle-data acquisition w38ebc66u. Working dir of the comparer: tools/benchmarks/stella-vs-trivy/.


1. Current state of the comparer

The lane builds 500 real OSS projects into OCI images and scans each with three scanners — Stella, Trivy (Aqua), Grype (Anchore). It measures only:

There is no ground truth — every metric is tool-vs-tool. The harness’s own analysis-problem-and-winning-groups.md documents a prior wrong “Trivy wins” conclusion that was a methodology confound (deployed-vs-all scope). This is the core motivation: tool-vs-tool counting is not just incomplete, it has actively misled.


2. The 10 evaluation dimensions vs current coverage

#DimensionCurrent coverage
1Package-discovery depthPartial — single PURL scalar, no by-type, no truth
2True-positive recallNone — tool-vs-tool only
3False-positive controlPartial — Trivy-fixture FP only, one-directional
4Evidence qualityNone — counts only
5Reachability / exploitabilityPartial — Stella-only heuristic; no KEV/EPSS surfaced
6SBOM compatibilityNone — never feeds foreign SBOMs to Stella
7Offline replay / determinismNone in the bench
8Remediation qualityNone measured (data present)
9Speed / footprint / air-gapNone captured
10Policy / audit storyNone

3. Locked decisions


4. Reporter / product gaps (Stella sbom check) — 14

Recurring theme: Stella computes/stores the signal but the offline read path projects a thin finding.

IDGapKey evidenceEffort
RPT-01CVSS-vector not emitted (present in adv at finding time)SbomCheckCommand.cs:415-442; reader CompactVulnDbReader.cs:140-147; exporter writes it VulnDbCommandGroup.cs:294,354trivial
RPT-02EPSS + CWE absent; exporter hard-codes them nullVulnDbCommandGroup.cs:354; schema cols exist CompactVulnDbSchema.cs:31-33; Postgres advisory_weaknesses 001_v1...sql:322-330small (re-export)
RPT-03Reachability verdict can’t distinguish not-run vs no-evidence vs not-observedSbomCheckCommand.cs:229-259, RenderJson :595-596; sinks Go/Rust only VulnDbCommandGroup.cs:445-541small
RPT-04Remediation: no fixedKind / minimal-upgrade; ranges mixed with versionsSbomCheckCommand.cs:435; LocalAdvisoryMatcher.cs:289-301; CompactVulnDbWriter.cs:140-141medium
EVID-01File-path/location dropped by the CLI’s lighter SbomParserScanner emits CycloneDxComposer.cs:360,378,466; parser has none ISbomParser.cs:96-123, SbomParser.cs:195-306medium
EVID-02Layer-digest droppedScanner emits CycloneDxComposer.cs:432, LanguageComponentMapper.cs:108; ignored SbomParser.cs:332-354small
EVID-03Advisory-source/feed provenance not exportedPostgres source_id→sources.key 001_v1...sql:202-216; exporter no join VulnDbCommandGroup.cs:250-266medium (v3)
EVID-04CPE parsed then discardedSbomParser.cs:222-248,556-587, ISbomParser.cs:101-102; dropped SbomCheckCommand.cs:211-214small
EVID-05No per-advisory match confidenceLocalAdvisoryMatcher.cs:29-35,248-266,350-352 (signals computed, discarded)medium
KEV-01KEV connector + vuln.kev_flags exist but never exported/surfacedConnector.Kev/KevConnector.cs:388-400; table 001_v1...sql:336-353; absent from CompactVulnDbSchema.csmedium (v3)
DET-01RFC-8785 canonical-id computed in Scanner, never stamped into sbom check outputcanonical-sbom-id-v1.md:9,92-97; RenderJson SbomCheckCommand.cs:546-617medium
DET-02No asserted byte-stability testSbomCheckReachabilityTests.cs, SbomCheckScopeTests.cs (neither asserts it)small
AUDIT-01sbom check never applies VEX (table + reader exist)vex table CompactVulnDbSchema.cs:45-52, reader CompactVulnDbReader.cs:151-175medium
AUDIT-02No signed/replayable offline decision capsule from sbom checkSignCommandGroup.cs (mock audit :467-472), ReplayCommandGroup, plain JSON SbomCheckCommand.cs:276-284large

v3 compact-DB bump batches EVID-03 + KEV-01 (new columns); RPT-02 rides the same re-export (exporter fix only). One re-export + mirror refresh covers all three.

Product open questions: EPSS source table for the join; EVID-05 confidence rubric; AUDIT-01 VEX default; AUDIT-02 capsule signing key; EVID-04 CPE synthesis policy.


5. Harness gaps — 17

IDGapEffortNotes
ORACLE-01Advisory-range labels from independent OSV data via reused EcosystemVersionFilterlargethe core; non-circular (raw OSV ≠ Stella DB)
ORACLE-02N-of-M consensus across independent lineagesmediumfallback proxy when no advisory range
ORACLE-03Backport/FP truth from distro trackerslargedistro-wins-OS precedence
ORACLE-045-tier label schema + per-tier precision/recall/F1 (unknown excluded → coverage%)mediumreuse stella bench framing
WIRE-01Determinism double-run, byte-identical findingssmallnormalize then sha256
WIRE-02SBOM interop: feed Syft/Trivy SBOMs to stella sbom check; schema-validate exportsmediumingest confirmed working
WIRE-03Speed / peak-RSS / DB-size / --network none assertionmediumtimings report-only, not gated
WIRE-04Remediation %: share of findings with actionable fixtrivialjq over existing artifacts
WIRE-05Discovery depth by type (os/lang/static-binary)smallprepare.json already has purlsByEco
TOOL-01osv-scanner column + consensus lineagemediumresolve-osv-scanner.sh
TOOL-02Clairdropped
TOOL-03Syft SBOM producer (feeds WIRE-02)smallresolve-syft.sh, no DB
METH-01Discovery-truth from lockfiles/distro manifests (rule-based)largedenominator for discovery P/R
METH-02Symmetric FP classification (Stella-only, Grype-only too)mediumgeneralize the annotator
METH-03VEX-suppression behavioral test via stella vex applymediumTier-2d correctness test
METH-04Evidence-quality scorer over each toolsmallwhere Stella richness shows
METH-05Signed decision-capsule for the bench’s own evidencemediumreuse stella attest

5a. Reachability dimension — “does the code reach the vulnerable call” (REACH-01…08)

Scope: genuine call-path reachability (the app’s code graph reaching the vulnerable function/sink), not scoring a metric. No competitor emits it (Trivy/Grype/osv-scanner) → Stella-only axis whose value is noise reduction: “of N found vulns, M are reachable-proven.” Source workflow wpbgy1si3.

Headline defect: the lane never runs the engine. compare-project.sh:292 calls sbom check with no --reachability/--source, so findings.reachability is always null and the regex estimate (:436-439) bins null into a permanent zero. The current reachability number is dead.

IDGap / actionEffort
REACH-01Wire --reachability --source <src> --reach-lang go|rust into the stella call for eligible cohortssmall
REACH-02Retain the FULL Go/Rust source tree (prepare uses sparse/deletes it; the SSA extractor needs the module tree)medium
REACH-03Pre-flight the external SSA tool (stella-callgraph-go/Rust); fail loud, never pass off the static fallback as the real enginesmall
REACH-04Replace the regex heuristic with the real wire verdict + reason + witnessCount (schema v3→v4)small
REACH-053-state status (engine-not-run / assessed / error); never emit fake not-observed for the 13 non-Go/Rust cohortssmall
REACH-06Cohort-level noise-reduction funnel in the launcher aggregate + reportmedium
REACH-07Cost control: reachability is an opt-in Go/Rust shard (~60 of ~340 projects) with per-project timeoutmedium
REACH-08Report sink coverage so unknown(no-sinks) is distinct from real not-observedsmall

Reporting model: funnel found → assessed → reachable (proven+likely) → proven, gated on reachabilityEngine.ran; headline reachableProvenRate = proven/assessed, noiseReductionRate = notObserved/assessed. Competitor columns stay N/A by design (that is the story). Coverage honestly bounded to Go+Rust today (9,077 sinks); all other cohorts shown as “reachability: not measured (no sinks)”.

Ground truth (no hand-labeling): verdict is self-certifying — proven requires a non-empty witness path; unknown(no-sinks) is rule-derived. Correctness/regression stays on the curated tier corpus (ReachabilityTierMetricHarness, promoted out of test asm — the same metrics engine ORACLE-04 reuses) + a precision-only “unreachable-by-rule” cross-check (advisory sink symbol provably absent from the call graph). Exploitability funnel: found → reachable:proven → KEV-listed → high-EPSS (reads KEV-01/RPT-02 fields).


6. Scoring reuse map (the big finding: ORACLE-04 is mostly assembly)

Substantial scoring already exists — and the highest-value pieces are all C#. ORACLE-04 becomes assembly of tested code + one new rule engine.

NeedReuse thisLocationLevel
Per-tier TP/FP/FN→P/R/F1 over a key-unionReachabilityTierMetricHarness.ComputeScanner.Reachability.Tests/.../ReachabilityTierCorpusTests.cs:358high (promote out of test asm; swap key→(image,pkg,CVE), enum→truth tiers)
Classify-then-count + Unknown bucket; P/R/F1 formulas; by-dimension dictMetricsCalculator / BenchmarkMetrics (orphaned singular Scanner.Benchmark)src/Scanner/__Libraries/StellaOps.Scanner.Benchmark/Metrics/*.cs:25,95,31-52,115-131high (revive + add tests; 0 csproj refs today)
Trivy/Grype/Syft → NormalizedFindingsingular Scanner.Benchmark adapters.../Harness/{Trivy,Grype,Syft}Adapter.cshigh (add osv-scanner + Stella adapters)
Finding ingestion (SARIF+JSON) + set-arith (overlap/Jaccard)VendorParityAnalyzerTools/StellaOps.Bench/.../VendorParityAnalyzer.cs:9,197high (truth-side ⇒ overlap=TP, truth-only=FN, scanner-only=FP)
Wired regression gate (warn/fail bands, delta, exit code, md/json report)KpiRegressionService + groundtruth baseline checkBinaryIndex.GroundTruth.Reproducible/Services/KpiRegressionService.cs:89,310high (DI-registered, CLI exposes thresholds)
Consensus label + confidence (weighted vote, margin, unanimity)VexConsensusEngine (+ TrustWeightEngine)VexLens.Core/Consensus/VexConsensusEngine.cs:88high (ORACLE-02; generalize VexStatus→truth tier)
Rule-weighted confidence shape + labeled-corpus accuracy gate templateLinksetCorrelationV2 + RealWorldLinksetAccuracyCorpusTests/AccuracyMatrixConcelier.Core/Linksets/LinksetCorrelationV2.cs:98high
Alias-aware (image,CVE) predictions = scorer INPUTlive compare-project.sh jqtools/benchmarks/stella-vs-trivy/scripts/compare-project.sh:402-460high (reuse the alias normalization)
Rule-based FP labels (fixture/placeholder) seedannotate-trivy-fixture-fps.pytools/benchmarks/.../annotate-trivy-fixture-fps.py:90,142medium

Do NOT reuse for evaluation: Policy EWS / AdvancedScoringEngine and Findings RiskEngine — those are risk prioritization (0-100 danger), orthogonal to correctness. Do NOT extend the doc’s stella bench run plural-Benchmarks path — it is unwired (not in CommandFactory; ICorpusRunner has no prod impl).

Genuinely net-new: the 5-tier TruthTier enum + the rule engine assigning labels per (image,pkg,CVE) (ORACLE-01/02/03 — nothing in the repo does (image,pkg,CVE) truth-labeling); the composite key; an osv-scanner + Stella-native adapter; promote the tier-metric harness; revive the orphaned Benchmark lib.


7. Oracle data — language/library lineages

Decision (2026-06-21): the oracle reads these sources from the local Concelier Postgres (vuln.advisory_affected), NOT by re-fetching. The URLs/formats below remain the authoritative map and the fallback for un-populated environments. Measured local coverage is in §10.

Authoritative URL/format map is already in the repo: every per-ecosystem language connector is a no-op alias of the single real OSV fetcher.

IDSourceUpstreamCoverage / notesLicense
osv-bulkOSV.dev per-ecosystem all.ziposv-vulnerabilities.storage.googleapis.com/<Eco>/all.zipOsvOptions.cs:9,21,42, OsvConnector.cs:705,736,1054~11/15 cohorts (npm-family, PyPI, Maven, Go, crates, Packagist, RubyGems, Hex, Pub). npm zip ~207 MB (OsvConnector.cs:113); set ~400-600 MB est.CC-BY-4.0
ghsaGitHub Security Advisoriesapi.github.com/advisoriesGhsaOptions.cs:9,25,33Closes dotnet + swift (absent from OSV buckets) → ~13/15. Needs a GitHub token. Enrichment/cross-check (also feeds OSV upstream).CC-BY-4.0
nvdNVD CVE API 2.0services.nvd.nist.gov/rest/json/cves/2.0CVE-level cross-walk only (CPE, not PURL) — tie-breaker, 0 version-range cohorts.US-gov public domain
osv-scanner-offline-dbosv-scanner bundlesame OSV upstreamDo NOT use as the oracle — circular (osv-scanner is a scored tool). Fetch raw all.zip instead.Apache-2.0 / CC-BY-4.0

Range-evaluator independence (recommended): reuse EcosystemVersionFilter.IsStillVulnerable (EcosystemVersionFilter.cs:36) fed raw upstream data — shares the algorithm, not the data, keeping it non-circular. A neutral re-implementation is only warranted to audit the comparator itself (then run both and diff).


8. Oracle data — distro lineages

Cohort distro exposure comes from the runtime base images the lane builds.

IDSourceUpstreamCoverage / notes
debian-trackerDebian Security Tracker JSONsecurity-tracker.debian.org/tracker/data/jsonDebianOptions.cs:71; fixed-in semantics DebianTrackerJsonParser.cs:29-58dotnet(aspnet)/python-slim/swift-slim/dart bases (bookworm/bullseye/trixie). ~75 MB.
ubuntuUSN JSON + CSAF/OVALubuntu.com/security/notices.json UbuntuOptions.cs:13; security-metadata.canonical.com/vex/vex-all.tar.xz UbuntuConnectorOptions.cs:17java cohort (eclipse-temurin jammy). Pick one canonical fixed-in source.
alpine-secdbAlpine secdbsecdb.alpinelinux.org/<rel>/<repo>.jsonAlpineOptions.cs:13,30,35, AlpineConnector.cs:363-366Highest — ~8-9 cohorts (cpp/go/rust/php/ruby/elixir/bun/deno + node:lts-alpine). ~20-60 MB.
redhat-hydraRed Hat CSAF (OVAL v2 alt)access.redhat.com/hydra/rest/securitydataRedHatOptions.cs:13,18; anon access RedHatRuntimeOverlay.cs:118~0-5% of current cohort. Keep lazy (fetch only if an rpm PURL appears).
suse-csafSUSE/openSUSE CSAFftp.suse.com/pub/projects/security/csaf/SuseOptions.cs:12,17~0% current. Lazy, future expansion only.

License-by-reference confirmed clean for all (incl. RHEL — fetch-by-reference, no committed data = consumer, not redistributor). Document the snapshot date per AGENTS.md §2.6.


9. Integration into the existing lane

oracle-sources.lock.json (committed — URLs + digests + versions ONLY)

{
  "schema": "stellaops.bench.oracleSources/v1",
  "generatedAt": "2026-06-21T00:00:00Z",
  "sources": [
    { "id": "debian-tracker-json", "distro": "debian", "releases": ["bookworm","bullseye","trixie"],
      "format": "json", "url": "https://security-tracker.debian.org/tracker/data/json",
      "sha256": "<hex>", "version": "snapshot-2026-06-21", "approxBytes": 78643200,
      "cachePath": "oracle/debian/data.json" },
    { "id": "alpine-secdb-v3.20-main", "distro": "alpine", "releases": ["v3.20"], "repo": "main",
      "format": "secdb", "url": "https://secdb.alpinelinux.org/v3.20/main.json",
      "sha256": "<hex>", "version": "v3.20", "cachePath": "oracle/alpine/v3.20/main.json" },
    { "id": "osv-npm", "ecosystem": "npm", "format": "osv-zip",
      "url": "https://osv-vulnerabilities.storage.googleapis.com/npm/all.zip",
      "sha256": "<hex>", "etag": "<etag>", "approxBytes": 217000000, "cachePath": "oracle/osv/npm/all.zip" }
    // redhat/suse marked "lazy": true (fetch only when an rpm PURL appears)
  ]
}

10. Coverage summary

Measured from the local store (stellaops_platform.vuln.advisory_affected, 2026-06-21) — distinct advisories per ecosystem:

Cohort(s)Local ecosystemDistinct advisories
js/ts/bun/denonpm178,406
python · go · java · phppypi · golang · maven · composer12,932 · 7,251 · 6,698 · 6,247
rust · ruby · dotnetcargo · gem · nuget2,505 · 2,055 · 1,832
elixir · swift · darthex · swift · pub99 · 18 · 13 (thin but present)
cpp(no conan; generic/cpe only)real gap → oracle-low-confidence
distrosdeb · rpm · apk57,787 · 1,768 · 8,848

So NuGet & Swift ARE present locally (the OSV-bucket concern is moot — GHSA backfill is already ingested); dart/elixir/swift are thin-but-present; cpp is the only true gap; distros deb/rpm/apk are strong. All from local data, zero fetch. Estimates below are superseded by the measured table above.


11. Open questions / to-confirm before/while planning

Data realities (only npm’s ~207 MB is verified in-repo; rest are estimates):

  1. Actual on-disk sizes of non-npm OSV zips, Debian data.json, Ubuntu vex-all.tar.xz, Alpine per-repo.
  2. Do NuGet (dotnet) and Swift have working OSV all.zip buckets now? (OSV connector deliberately omits them — in-repo signal they were unreliable.)
  3. cpp authoritative truth source — likely mark cpp findings oracle-low-confidence.
  4. Ubuntu canonical fixed-in source: USN JSON vs CSAF vex-all vs per-release OVAL.
  5. Are bench SBOM OS-package PURLs release-qualified (so branch selection is unambiguous)?

Provisioning / determinism / posture: 6. GitHub token (GHSA) + NVD API key — where do they come from in the air-gapped/CI lane? 7. Snapshot-freeze: trackers update continuously → lock pins a snapshot, resolve fails-closed on drift; lock refreshed deliberately, not per run. 8. Re-confirm RHEL/SUSE fetch-by-reference posture + record snapshot date (§2.6/§2.8).

Scorer: 9. Granularity (image,pkg,CVE) — confirm every scanner carries package identity at compare time (Trivy PkgName / Grype artifact.name / Stella component). 10. Which (scanner,tier) the regression gate fires on; is coverage% gated or report-only.


12. Sources