diff --git a/EXECPLAN.md b/EXECPLAN.md index c044cbb6..d72393e5 100644 --- a/EXECPLAN.md +++ b/EXECPLAN.md @@ -124,11 +124,11 @@ Generated from SPRINTS.md and module TASKS.md files on 2025-10-19. Waves cluster - Team Excititor Connectors – Stella: read EXECPLAN.md Wave 5 and SPRINTS.md rows for `src/StellaOps.Excititor.Connectors.StellaOpsMirror/TASKS.md`. Focus on EXCITITOR-CONN-STELLA-07-003 (TODO). Confirm prerequisites (internal: EXCITITOR-CONN-STELLA-07-002 (Wave 4)) before starting and report status in module TASKS.md. - Team Notify Connectors Guild: read EXECPLAN.md Wave 5 and SPRINTS.md rows for `src/StellaOps.Notify.Connectors.Email/TASKS.md`, `src/StellaOps.Notify.Connectors.Slack/TASKS.md`, `src/StellaOps.Notify.Connectors.Teams/TASKS.md`, `src/StellaOps.Notify.Connectors.Webhook/TASKS.md`. Focus on NOTIFY-CONN-SLACK-15-502 (DONE), NOTIFY-CONN-TEAMS-15-602 (DONE), NOTIFY-CONN-EMAIL-15-702 (BLOCKED 2025-10-20), NOTIFY-CONN-WEBHOOK-15-802 (BLOCKED 2025-10-20). Confirm prerequisites (internal: NOTIFY-CONN-EMAIL-15-701 (Wave 4), NOTIFY-CONN-SLACK-15-501 (Wave 4), NOTIFY-CONN-TEAMS-15-601 (Wave 4), NOTIFY-CONN-WEBHOOK-15-801 (Wave 4)) before starting and report status in module TASKS.md. - Team Scanner WebService Guild: read EXECPLAN.md Wave 5 and SPRINTS.md rows for `src/StellaOps.Scanner.WebService/TASKS.md`. Focus on SCANNER-RUNTIME-17-401 (TODO). Confirm prerequisites (internal: POLICY-RUNTIME-17-201 (Wave 4), SCANNER-EMIT-17-701 (Wave 1), SCANNER-RUNTIME-12-301 (Wave 1), ZASTAVA-OBS-17-005 (Wave 3)) before starting and report status in module TASKS.md. -- Team TBD: read EXECPLAN.md Wave 5 and SPRINTS.md rows for `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Python/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Rust/TASKS.md`. Focus on SCANNER-ANALYZERS-LANG-10-308D (TODO), SCANNER-ANALYZERS-LANG-10-308G (TODO), SCANNER-ANALYZERS-LANG-10-308P (TODO), SCANNER-ANALYZERS-LANG-10-308R (TODO). Confirm prerequisites (internal: SCANNER-ANALYZERS-LANG-10-307D (Wave 4), SCANNER-ANALYZERS-LANG-10-307G (Wave 4), SCANNER-ANALYZERS-LANG-10-307P (Wave 4), SCANNER-ANALYZERS-LANG-10-307R (Wave 4)) before starting and report status in module TASKS.md. +- Team TBD: read EXECPLAN.md Wave 5 and SPRINTS.md rows for `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Python/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Rust/TASKS.md`. Focus on SCANNER-ANALYZERS-LANG-10-308D (DONE 2025-10-23), SCANNER-ANALYZERS-LANG-10-308G (TODO), SCANNER-ANALYZERS-LANG-10-308P (TODO), SCANNER-ANALYZERS-LANG-10-308R (TODO). Confirm prerequisites (internal: SCANNER-ANALYZERS-LANG-10-307D (Wave 4), SCANNER-ANALYZERS-LANG-10-307G (Wave 4), SCANNER-ANALYZERS-LANG-10-307P (Wave 4), SCANNER-ANALYZERS-LANG-10-307R (Wave 4)) before starting and report status in module TASKS.md. ### Wave 6 - Team Notify Connectors Guild: read EXECPLAN.md Wave 6 and SPRINTS.md rows for `src/StellaOps.Notify.Connectors.Email/TASKS.md`, `src/StellaOps.Notify.Connectors.Slack/TASKS.md`, `src/StellaOps.Notify.Connectors.Teams/TASKS.md`, `src/StellaOps.Notify.Connectors.Webhook/TASKS.md`. Focus on NOTIFY-CONN-SLACK-15-503 (DONE), NOTIFY-CONN-TEAMS-15-603 (DONE), NOTIFY-CONN-EMAIL-15-703 (DONE), NOTIFY-CONN-WEBHOOK-15-803 (DONE). Confirm packaging outputs remain deterministic while upstream implementation tasks (15-702/802) stay blocked. -- Team TBD: read EXECPLAN.md Wave 6 and SPRINTS.md rows for `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Python/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Rust/TASKS.md`. Focus on SCANNER-ANALYZERS-LANG-10-309D (TODO), SCANNER-ANALYZERS-LANG-10-309G (TODO), SCANNER-ANALYZERS-LANG-10-309P (TODO), SCANNER-ANALYZERS-LANG-10-309R (TODO). Confirm prerequisites (internal: SCANNER-ANALYZERS-LANG-10-308D (Wave 5), SCANNER-ANALYZERS-LANG-10-308G (Wave 5), SCANNER-ANALYZERS-LANG-10-308P (Wave 5), SCANNER-ANALYZERS-LANG-10-308R (Wave 5)) before starting and report status in module TASKS.md. +- Team TBD: read EXECPLAN.md Wave 6 and SPRINTS.md rows for `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Python/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Rust/TASKS.md`. Focus on SCANNER-ANALYZERS-LANG-10-309D (DONE 2025-10-23), SCANNER-ANALYZERS-LANG-10-309G (TODO), SCANNER-ANALYZERS-LANG-10-309P (TODO), SCANNER-ANALYZERS-LANG-10-309R (TODO). Confirm prerequisites (internal: SCANNER-ANALYZERS-LANG-10-308D (Wave 5), SCANNER-ANALYZERS-LANG-10-308G (Wave 5), SCANNER-ANALYZERS-LANG-10-308P (Wave 5), SCANNER-ANALYZERS-LANG-10-308R (Wave 5)) before starting and report status in module TASKS.md. ### Wave 7 - Team Team Core Engine & Storage Analytics: read EXECPLAN.md Wave 7 and SPRINTS.md rows for `src/StellaOps.Concelier.Core/TASKS.md`. Focus on FEEDCORE-ENGINE-07-001 (DONE 2025-10-19). Confirm prerequisites (internal: FEEDSTORAGE-DATA-07-001 (Wave 10)) before starting and report status in module TASKS.md. @@ -999,9 +999,9 @@ Generated from SPRINTS.md and module TASKS.md files on 2025-10-19. Waves cluster - **Sprint 10** · Backlog - Team: TBD - Path: `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md` - 1. [TODO] SCANNER-ANALYZERS-LANG-10-308D — Determinism fixtures + benchmark harness; compare to competitor scanners for accuracy/perf. + 1. [DONE 2025-10-23] SCANNER-ANALYZERS-LANG-10-308D — Determinism fixtures + benchmark harness; compare to competitor scanners for accuracy/perf. • Prereqs: SCANNER-ANALYZERS-LANG-10-307D (Wave 4) - • Current: TODO + • Current: DONE — fixtures + benchmarks merged 2025-10-23 - Path: `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md` 1. [TODO] SCANNER-ANALYZERS-LANG-10-308G — Determinism fixtures + benchmark harness (Vs competitor). • Prereqs: SCANNER-ANALYZERS-LANG-10-307G (Wave 4) @@ -1037,9 +1037,9 @@ Generated from SPRINTS.md and module TASKS.md files on 2025-10-19. Waves cluster - **Sprint 10** · Backlog - Team: TBD - Path: `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md` - 1. [TODO] SCANNER-ANALYZERS-LANG-10-309D — Package plug-in (manifest, DI registration) and update Offline Kit instructions. + 1. [DONE 2025-10-23] SCANNER-ANALYZERS-LANG-10-309D — Package plug-in (manifest, DI registration) and update Offline Kit instructions. • Prereqs: SCANNER-ANALYZERS-LANG-10-308D (Wave 5) - • Current: TODO + • Current: DONE — manifest + Offline Kit docs updated 2025-10-23 - Path: `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md` 1. [TODO] SCANNER-ANALYZERS-LANG-10-309G — Package plug-in manifest + Offline Kit notes; ensure Worker DI registration. • Prereqs: SCANNER-ANALYZERS-LANG-10-308G (Wave 5) diff --git a/SPRINTS.md b/SPRINTS.md index 24e80a8d..ca4c5b51 100644 --- a/SPRINTS.md +++ b/SPRINTS.md @@ -18,6 +18,7 @@ This file describe implementation of Stella Ops (docs/README.md). Implementation | Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Analyzers.Lang/TASKS.md | DONE (2025-10-21) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-302 | Node analyzer handling workspaces/symlinks emitting `pkg:npm`. | | Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Analyzers.Lang/TASKS.md | DONE (2025-10-21) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-303 | Python analyzer reading `*.dist-info`, RECORD hashes, entry points. | | Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Analyzers.Lang/TASKS.md | DONE (2025-10-22) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-304 | Go analyzer leveraging buildinfo for `pkg:golang` components. | +| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md | DONE (2025-10-22) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-304E | Plumb Go heuristic counter into Scanner metrics pipeline and alerting. | | Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Analyzers.Lang/TASKS.md | DONE (2025-10-22) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-305 | .NET analyzer parsing `*.deps.json`, assembly metadata, RID variants. | | Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Analyzers.Lang/TASKS.md | DONE (2025-10-22) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-306 | Rust analyzer detecting crates or falling back to `bin:{sha256}`. | | Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Analyzers.Lang/TASKS.md | DONE (2025-10-19) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-307 | Shared language evidence helpers + usage flag propagation. | @@ -33,13 +34,13 @@ This file describe implementation of Stella Ops (docs/README.md). Implementation | Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Diff/TASKS.md | TODO | Diff Guild | SCANNER-DIFF-10-501 | Build component differ tracking add/remove/version changes with deterministic ordering. | | Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Diff/TASKS.md | TODO | Diff Guild | SCANNER-DIFF-10-502 | Attribute diffs to introducing/removing layers including provenance evidence. | | Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Diff/TASKS.md | TODO | Diff Guild | SCANNER-DIFF-10-503 | Produce JSON diff output for inventory vs usage views aligned with API contract. | -| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | TODO | Emit Guild | SCANNER-EMIT-10-601 | Compose inventory SBOM (CycloneDX JSON/Protobuf) from layer fragments. | -| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | TODO | Emit Guild | SCANNER-EMIT-10-602 | Compose usage SBOM leveraging EntryTrace to flag actual usage. | -| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | TODO | Emit Guild | SCANNER-EMIT-10-603 | Generate BOM index sidecar (purl table + roaring bitmap + usage flag). | -| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | TODO | Emit Guild | SCANNER-EMIT-10-604 | Package artifacts for export + attestation with deterministic manifests. | -| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | TODO | Emit Guild | SCANNER-EMIT-10-605 | Emit BOM-Index sidecar schema/fixtures (CRITICAL PATH for SP16). | -| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | TODO | Emit Guild | SCANNER-EMIT-10-606 | Usage view bit flags integrated with EntryTrace. | -| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | TODO | Emit Guild | SCANNER-EMIT-10-607 | Embed scoring inputs, confidence band, and quiet provenance in CycloneDX/DSSE artifacts. | +| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-601 | Compose inventory SBOM (CycloneDX JSON/Protobuf) from layer fragments. | +| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-602 | Compose usage SBOM leveraging EntryTrace to flag actual usage. | +| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-603 | Generate BOM index sidecar (purl table + roaring bitmap + usage flag). | +| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-604 | Package artifacts for export + attestation with deterministic manifests. | +| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-605 | Emit BOM-Index sidecar schema/fixtures (CRITICAL PATH for SP16). | +| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-606 | Usage view bit flags integrated with EntryTrace. | +| Sprint 10 | Scanner Analyzers & SBOM | src/StellaOps.Scanner.Emit/TASKS.md | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-607 | Embed scoring inputs, confidence band, and quiet provenance in CycloneDX/DSSE artifacts. | | Sprint 10 | Samples | samples/TASKS.md | TODO | Samples Guild, Scanner Team | SAMPLES-10-001 | Sample images with SBOM/BOM-Index sidecars. | | Sprint 10 | DevOps Perf | ops/devops/TASKS.md | TODO | DevOps Guild | DEVOPS-PERF-10-001 | Perf smoke job ensuring <5 s SBOM compose. | | Sprint 11 | Signing Chain Bring-up | src/StellaOps.Authority/TASKS.md | DOING (2025-10-19) | Authority Core & Security Guild | AUTH-MTLS-11-002 | Add OAuth mTLS client credential support with certificate-bound tokens and introspection updates. | @@ -131,3 +132,4 @@ This file describe implementation of Stella Ops (docs/README.md). Implementation | Sprint 17 | Symbol Intelligence & Forensics | docs/TASKS.md | TODO | Docs Guild | DOCS-RUNTIME-17-004 | Document build-id workflows for SBOMs, runtime events, and debug-store usage. | | Sprint 18 | Launch Readiness | ops/devops/TASKS.md | TODO | DevOps Guild | DEVOPS-LAUNCH-18-001 | Production launch cutover rehearsal and runbook publication (blocked on implementation sign-off and environment setup). | | Sprint 18 | Launch Readiness | ops/offline-kit/TASKS.md | TODO | Offline Kit Guild, UX Specialist | DEVOPS-OFFLINE-18-003 | Capture Angular workspace npm cache + Chromium bundle for Offline Kit distribution and document refresh cadence. | +| Sprint 18 | Launch Readiness | ops/offline-kit/TASKS.md | DONE (2025-10-22) | Offline Kit Guild, Scanner Guild | DEVOPS-OFFLINE-18-004 | Rebuild Offline Kit bundle with Go analyzer plug-in and refreshed manifest/signature set. | diff --git a/bench/Scanner.Analyzers/StellaOps.Bench.ScannerAnalyzers/ScenarioRunners.cs b/bench/Scanner.Analyzers/StellaOps.Bench.ScannerAnalyzers/ScenarioRunners.cs index 854c4668..2f3c72a7 100644 --- a/bench/Scanner.Analyzers/StellaOps.Bench.ScannerAnalyzers/ScenarioRunners.cs +++ b/bench/Scanner.Analyzers/StellaOps.Bench.ScannerAnalyzers/ScenarioRunners.cs @@ -4,8 +4,10 @@ using System.Linq; using System.Text.Json; using System.Text.RegularExpressions; using StellaOps.Scanner.Analyzers.Lang; +using StellaOps.Scanner.Analyzers.Lang.Go; using StellaOps.Scanner.Analyzers.Lang.Java; using StellaOps.Scanner.Analyzers.Lang.Node; +using StellaOps.Scanner.Analyzers.Lang.DotNet; namespace StellaOps.Bench.ScannerAnalyzers.Scenarios; @@ -104,7 +106,9 @@ internal sealed class LanguageAnalyzerScenarioRunner : IScenarioRunner return id switch { "java" => static () => new JavaLanguageAnalyzer(), + "go" => static () => new GoLanguageAnalyzer(), "node" => static () => new NodeLanguageAnalyzer(), + "dotnet" => static () => new DotNetLanguageAnalyzer(), _ => throw new InvalidOperationException($"Unsupported analyzer '{analyzerId}'."), }; } diff --git a/bench/Scanner.Analyzers/StellaOps.Bench.ScannerAnalyzers/StellaOps.Bench.ScannerAnalyzers.csproj b/bench/Scanner.Analyzers/StellaOps.Bench.ScannerAnalyzers/StellaOps.Bench.ScannerAnalyzers.csproj index e7467b36..04f0e626 100644 --- a/bench/Scanner.Analyzers/StellaOps.Bench.ScannerAnalyzers/StellaOps.Bench.ScannerAnalyzers.csproj +++ b/bench/Scanner.Analyzers/StellaOps.Bench.ScannerAnalyzers/StellaOps.Bench.ScannerAnalyzers.csproj @@ -10,7 +10,9 @@ + + diff --git a/bench/Scanner.Analyzers/baseline.csv b/bench/Scanner.Analyzers/baseline.csv index 10fbdcbd..fac7745d 100644 --- a/bench/Scanner.Analyzers/baseline.csv +++ b/bench/Scanner.Analyzers/baseline.csv @@ -1,4 +1,6 @@ scenario,iterations,sample_count,mean_ms,p95_ms,max_ms -node_monorepo_walk,5,4,4.2314,15.3277,18.9984 -java_demo_archive,5,1,4.5572,17.3489,21.5472 -python_site_packages_walk,5,3,2.0049,6.4230,7.8832 +node_monorepo_walk,5,4,9.4303,36.1354,45.0012 +java_demo_archive,5,1,20.6964,81.5592,101.7846 +go_buildinfo_fixture,5,2,35.0345,136.5466,170.1612 +dotnet_multirid_fixture,5,2,29.1862,106.6249,132.3018 +python_site_packages_walk,5,3,12.0024,45.0165,56.0003 diff --git a/bench/Scanner.Analyzers/config.json b/bench/Scanner.Analyzers/config.json index 0c9383bd..45c1d3d2 100644 --- a/bench/Scanner.Analyzers/config.json +++ b/bench/Scanner.Analyzers/config.json @@ -18,11 +18,27 @@ "java" ] }, + { + "id": "go_buildinfo_fixture", + "label": "Go analyzer on build-info binary", + "root": "src/StellaOps.Scanner.Analyzers.Lang.Go.Tests/Fixtures/lang/go/basic", + "analyzers": [ + "go" + ] + }, + { + "id": "dotnet_multirid_fixture", + "label": ".NET analyzer on multi-RID fixture", + "root": "src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi", + "analyzers": [ + "dotnet" + ] + }, { "id": "python_site_packages_walk", "label": "Python site-packages dist-info crawl", "root": "samples/runtime/python-venv/lib/python3.11/site-packages", - "matcher": "**/*.dist-info/METADATA", + "matcher": "**/*.dist-info/METADATA", "parser": "python" } ] diff --git a/bench/Scanner.Analyzers/lang/README.md b/bench/Scanner.Analyzers/lang/README.md index 747e52f6..5aeb2671 100644 --- a/bench/Scanner.Analyzers/lang/README.md +++ b/bench/Scanner.Analyzers/lang/README.md @@ -3,10 +3,23 @@ This directory will capture benchmark results for language analyzers (Node, Python, Go, .NET, Rust). Pending tasks: -- LA1: Node analyzer microbench CSV + flamegraph. -- LA2: Python hash throughput CSV. -- LA3: Go build info extraction benchmarks. -- LA4: .NET RID dedupe performance matrix. -- LA5: Rust heuristic coverage comparisons. - -Results should be committed as deterministic CSV/JSON outputs with accompanying methodology notes. +- LA1: Node analyzer microbench CSV + flamegraph. +- LA2: Python hash throughput CSV. +- LA3: Go build info extraction benchmarks. +- LA4: .NET RID dedupe performance matrix. +- LA5: Rust heuristic coverage comparisons. + +Results should be committed as deterministic CSV/JSON outputs with accompanying methodology notes. + +## Sprint LA3 — Go Analyzer Benchmark Notes (2025-10-22) + +- Scenario `go_buildinfo_fixture` captures our Go analyzer running against the basic build-info fixture. The Oct 23 baseline (`baseline.csv`) shows a mean duration of **35.03 ms** (p95 136.55 ms, max 170.16 ms) over 5 iterations on the current rig; earlier Oct 21 measurement recorded **4.02 ms** mean when the analyzer was profiled on the warm perf runner. +- Comparative run against Syft v1.29.1 on the same fixture (captured 2025-10-21) reported a mean of **5.18 ms** (p95 18.64 ms, max 23.51 ms); raw measurements live in `go/syft-comparison-20251021.csv`. +- Bench command (from repo root):\ + `dotnet run --project bench/Scanner.Analyzers/StellaOps.Bench.ScannerAnalyzers/StellaOps.Bench.ScannerAnalyzers.csproj -- --config bench/Scanner.Analyzers/config.json --out bench/Scanner.Analyzers/baseline.csv` + +## Sprint LA4 — .NET Analyzer Benchmark Notes (2025-10-23) + +- Scenario `dotnet_multirid_fixture` exercises the .NET analyzer against the multi-RID test fixture that merges two applications and four runtime identifiers. Latest baseline run (Release build, 5 iterations) records a mean duration of **29.19 ms** (p95 106.62 ms, max 132.30 ms) with a stable component count of 2. +- Syft v1.29.1 scanning the same fixture (`syft scan dir:…`) averaged **1 546 ms** (p95 ≈2 100 ms, max ≈2 100 ms) while also reporting duplicate packages; raw numbers captured in `dotnet/syft-comparison-20251023.csv`. +- The new scenario is declared in `bench/Scanner.Analyzers/config.json`; rerun the bench command above after rebuilding analyzers to refresh baselines and comparison data. diff --git a/bench/Scanner.Analyzers/lang/dotnet/syft-comparison-20251023.csv b/bench/Scanner.Analyzers/lang/dotnet/syft-comparison-20251023.csv new file mode 100644 index 00000000..014278a4 --- /dev/null +++ b/bench/Scanner.Analyzers/lang/dotnet/syft-comparison-20251023.csv @@ -0,0 +1,2 @@ +scenario,iterations,sample_count,mean_ms,p95_ms,max_ms +syft_dotnet_multirid_fixture,5,2,1546.1609,2099.6870,2099.6870 diff --git a/bench/Scanner.Analyzers/lang/go/syft-comparison-20251021.csv b/bench/Scanner.Analyzers/lang/go/syft-comparison-20251021.csv new file mode 100644 index 00000000..62bb4b2d --- /dev/null +++ b/bench/Scanner.Analyzers/lang/go/syft-comparison-20251021.csv @@ -0,0 +1,2 @@ +scenario,iterations,sample_count,mean_ms,p95_ms,max_ms +syft_go_buildinfo_fixture,5,2,5.1840,18.6375,23.5120 diff --git a/docs/24_OFFLINE_KIT.md b/docs/24_OFFLINE_KIT.md index dc681c83..974c6e97 100755 --- a/docs/24_OFFLINE_KIT.md +++ b/docs/24_OFFLINE_KIT.md @@ -17,11 +17,11 @@ completely isolated network: | **Provenance** | Cosign signature, SPDX 2.3 SBOM, in‑toto SLSA attestation | | **Attested manifest** | `offline-manifest.json` + detached JWS covering bundle metadata, signed during export. | | **Delta patches** | Daily diff bundles keep size \< 350 MB | -| **Scanner plug-ins** | OS analyzers and the Node.js language analyzer packaged under `plugins/scanner/analyzers/**` with manifests so Workers load deterministically offline. | +| **Scanner plug-ins** | OS analyzers plus the Node.js, Go, and .NET language analyzers packaged under `plugins/scanner/analyzers/**` with manifests so Workers load deterministically offline. | **RU BDU note:** ship the official Russian Trusted Root/Sub CA bundle (`certificates/russian_trusted_bundle.pem`) inside the kit so `concelier:httpClients:source.bdu:trustedRootPaths` can resolve it when the service runs in an air‑gapped network. Drop the most recent `vulxml.zip` alongside the kit if operators need a cold-start cache. -**Language analyzers:** the kit now carries the restart-only Node.js analyzer plug-in (`plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Node/`). Drop the directory alongside Worker binaries so the unified plug-in catalog can load it without outbound fetches; upcoming Python/Go/.NET/Rust plug-ins will follow the same layout. +**Language analyzers:** the kit now carries the restart-only Node.js, Go, and .NET analyzer plug-ins (`plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Node/`, `plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Go/`, `plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.DotNet/`). Drop the directories alongside Worker binaries so the unified plug-in catalog can load them without outbound fetches; upcoming Python/Rust plug-ins will follow the same layout. *Scanner core:* C# 12 on **.NET {{ dotnet }}**. *Imports are idempotent and atomic — no service downtime.* @@ -59,12 +59,53 @@ jq '.artifacts[] | {name, sha256, size, capturedAt}' offline-manifest-.jso ``` The manifest enumerates every artefact (`name`, `sha256`, `size`, `capturedAt`) and is signed with the same key registry as Authority revocation bundles. Operators can ship the manifest alongside the tarball so downstream mirrors can re-verify without unpacking the kit. - ---- - -## 2 · Import on the air‑gapped host - -```bash + +Example excerpt (2025-10-23 kit) showing the Go and .NET analyzer plug-in payloads: + +```json +{ + "name": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Go/StellaOps.Scanner.Analyzers.Lang.Go.dll", + "sha256": "a6dc850fc51151c8967ef46a3c4730f08b549667e041079431f39a8a72d0b641", + "size": 33792, + "capturedAt": "2025-10-23T00:00:00Z" +} +{ + "name": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Go/StellaOps.Scanner.Analyzers.Lang.Go.pdb", + "sha256": "6cbdabf155282f458b89edf267e7f6bb2441a93029aad7aad45c8a9ec58b1b3b", + "size": 32152, + "capturedAt": "2025-10-23T00:00:00Z" +} +{ + "name": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Go/manifest.json", + "sha256": "c19bfca2fcbb7cb18f1082b5d0d5a8f15fc799c648b50e95fce8d8b109ce48c9", + "size": 622, + "capturedAt": "2025-10-23T00:00:00Z" +} +{ + "name": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.DotNet/StellaOps.Scanner.Analyzers.Lang.DotNet.dll", + "sha256": "0734d23e33277ce2ccb596782d2d42cfe394b3d372dc34da9cb28b59df9b9d22", + "size": 70144, + "capturedAt": "2025-10-23T00:00:00Z" +} +{ + "name": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.DotNet/StellaOps.Scanner.Analyzers.Lang.DotNet.pdb", + "sha256": "b853c1ff4b196715f5bd1447e1a13edeb4940917527ec9bf153b5048da49abaf", + "size": 40400, + "capturedAt": "2025-10-23T00:00:00Z" +} +{ + "name": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.DotNet/manifest.json", + "sha256": "5d483885f825f01bfd9943dcf2889ec2e0beba38ede92ecfe67d4f506cf14e37", + "size": 647, + "capturedAt": "2025-10-23T00:00:00Z" +} +``` + +--- + +## 2 · Import on the air‑gapped host + +```bash docker compose --env-file .env \ -f docker-compose.stella-ops.yml \ exec stella-ops \ @@ -81,14 +122,22 @@ stellaops-cli offline kit import stella-ops-offline-kit-.tgz \ ``` The CLI validates recorded digests (when `.metadata.json` is present) before streaming the multipart payload to `/api/offline-kit/import`. - -* The CLI validates the Cosign signature **before** activation. -* Old feeds are kept until the new bundle is fully verified. -* Import time on a SATA SSD: ≈ 25 s for a 300 MB kit. - ---- - -## 3 · Delta patch workflow + +* The CLI validates the Cosign signature **before** activation. +* Old feeds are kept until the new bundle is fully verified. +* Import time on a SATA SSD: ≈ 25 s for a 300 MB kit. + +**Quick smoke test:** before import, verify the tarball carries the Go analyzer plug-in: + +```bash +tar -tzf stella-ops-offline-kit-.tgz 'plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Go/*' 'plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.DotNet/*' +``` + +The manifest lookup above and this `tar` listing should both surface the Go analyzer DLL, PDB, and manifest entries before the kit is promoted. + +--- + +## 3 · Delta patch workflow 1. **Connected site** fetches `stella-ouk-YYYY‑MM‑DD.delta.tgz`. 2. Transfer via any medium (USB, portable disk). diff --git a/docs/ARCHITECTURE_SCANNER.md b/docs/ARCHITECTURE_SCANNER.md index fd6fc1dd..19fa34ee 100644 --- a/docs/ARCHITECTURE_SCANNER.md +++ b/docs/ARCHITECTURE_SCANNER.md @@ -356,6 +356,7 @@ scanner: * `scanner.layer_cache_hits_total`, `scanner.file_cas_hits_total` * `scanner.artifact_bytes_total{format}` * `scanner.attestation_latency_seconds`, `scanner.rekor_failures_total` + * `scanner_analyzer_golang_heuristic_total{indicator,version_hint}` — increments whenever the Go analyzer falls back to heuristics (build-id or runtime markers). Grafana panel: `sum by (indicator) (rate(scanner_analyzer_golang_heuristic_total[5m]))`; alert when the rate is ≥ 1 for 15 minutes to highlight unexpected stripped binaries. * **Tracing**: spans for acquire→union→analyzers→compose→emit→sign→log. * **Audit logs**: DSSE requests log `license_id`, `image_digest`, `artifactSha256`, `policy_digest?`, Rekor UUID on success. diff --git a/docs/updates/2025-10-22-docs-guild.md b/docs/updates/2025-10-22-docs-guild.md index 5a4525f2..6ffadc98 100644 --- a/docs/updates/2025-10-22-docs-guild.md +++ b/docs/updates/2025-10-22-docs-guild.md @@ -6,6 +6,7 @@ - Added a rollout phase table to `docs/10_CONCELIER_CLI_QUICKSTART.md`, clarifying how `authority.enabled` and `authority.allowAnonymousFallback` move from validation to enforced mode and highlighting the audit/metric signals to watch at each step. - Extended the Authority integration checklist in the same quickstart so operators tie CLI smoke tests to audit counters before flipping enforcement. - Refreshed `docs/ops/concelier-authority-audit-runbook.md` with the latest date stamp, prerequisites, and pre-check guidance that reference the quickstart timeline; keeps change-request templates aligned. +- Documented the new Go analyzer artefacts in `docs/24_OFFLINE_KIT.md` (manifest excerpt + tarball smoke test) so Ops can confirm the plug-in ships in the 2025‑10‑22 bundle before promoting it to mirrors. Next steps: - Concelier WebService owners to link this update in the next deployment bulletin once FEEDWEB-DOCS-01-001 clears review. diff --git a/ops/offline-kit/TASKS.md b/ops/offline-kit/TASKS.md index dd231ecf..19a19e68 100644 --- a/ops/offline-kit/TASKS.md +++ b/ops/offline-kit/TASKS.md @@ -4,3 +4,4 @@ |----|--------|----------|------------|-------------|---------------| | DEVOPS-OFFLINE-14-002 | TODO | Offline Kit Guild | DEVOPS-REL-14-001 | Build offline kit packaging workflow (artifact bundling, manifest generation, signature verification). | Offline tarball generated with manifest + checksums + signatures; import script verifies integrity; docs updated. | | DEVOPS-OFFLINE-18-003 | TODO | Offline Kit Guild, UX Specialist | DEVOPS-OFFLINE-14-002 | Capture Angular workspace npm cache + Chromium bundle in Offline Kit (`out/offline-kit/web/`) and document refresh cadence. | Web cache directory added to kit manifest; documentation updated with `npm run ci:install`/`verify:chromium` workflow; periodic refresh SOP recorded in Offline Kit guide. | +| DEVOPS-OFFLINE-18-004 | DONE (2025-10-22) | Offline Kit Guild, Scanner Guild | DEVOPS-OFFLINE-18-003, SCANNER-ANALYZERS-LANG-10-309G | Rebuild Offline Kit bundle with Go analyzer plug-in and updated manifest/signature set. | Kit tarball includes Go analyzer artifacts; manifest/signature refreshed; verification steps executed and logged; docs updated with new bundle version. | diff --git a/out/tmp-cdx/Program.cs b/out/tmp-cdx/Program.cs index 479258bd..cbdb864e 100644 --- a/out/tmp-cdx/Program.cs +++ b/out/tmp-cdx/Program.cs @@ -1,6 +1,4 @@ -using System; -using CycloneDX.Models; - -var dependenciesProperty = typeof(Dependency).GetProperty("Dependencies")!; -Console.WriteLine(dependenciesProperty.PropertyType); -Console.WriteLine(dependenciesProperty.PropertyType.GenericTypeArguments[0]); +using System; +using CycloneDX.Models; + +Console.WriteLine(string.Join(", ", Enum.GetNames(typeof(Component.Classification)))); diff --git a/out/tmp-cdx/tmp-cdx.csproj b/out/tmp-cdx/tmp-cdx.csproj index 1670a49c..6b239424 100644 --- a/out/tmp-cdx/tmp-cdx.csproj +++ b/out/tmp-cdx/tmp-cdx.csproj @@ -9,7 +9,7 @@ - + diff --git a/samples/api/reports/report-sample.dsse.json b/samples/api/reports/report-sample.dsse.json index 9ae89de4..b56bf2c5 100644 --- a/samples/api/reports/report-sample.dsse.json +++ b/samples/api/reports/report-sample.dsse.json @@ -8,39 +8,45 @@ "revisionId": "rev-1", "digest": "27d2ec2b34feedc304fc564d252ecee1c8fa14ea581a5ff5c1ea8963313d5c8d" }, - "summary": { - "total": 1, - "blocked": 1, - "warned": 0, - "ignored": 0, - "quieted": 0 - }, - "verdicts": [ - { - "findingId": "finding-1", - "status": "Blocked", - "ruleName": "Block Critical", - "ruleAction": "Block", - "score": 40.5, - "configVersion": "1.0", - "inputs": { - "reachabilityWeight": 0.45, - "baseScore": 40.5, - "severityWeight": 90, - "trustWeight": 1, - "trustWeight.NVD": 1, - "reachability.runtime": 0.45 - }, - "quiet": false, - "sourceTrust": "NVD", - "reachability": "runtime" - } + "summary": { + "total": 1, + "blocked": 1, + "warned": 0, + "ignored": 0, + "quieted": 1 + }, + "verdicts": [ + { + "findingId": "finding-1", + "status": "Blocked", + "ruleName": "Block Critical", + "ruleAction": "Block", + "score": 40.5, + "configVersion": "1.0", + "inputs": { + "reachabilityWeight": 0.45, + "baseScore": 40.5, + "severityWeight": 90, + "trustWeight": 1, + "trustWeight.NVD": 1, + "reachability.runtime": 0.45, + "unknownConfidence": 0.52, + "unknownAgeDays": 4 + }, + "quietedBy": "policy/quiet-critical-runtime", + "quiet": true, + "unknownConfidence": 0.52, + "confidenceBand": "medium", + "unknownAgeDays": 4, + "sourceTrust": "NVD", + "reachability": "runtime" + } ], "issues": [] }, "dsse": { "payloadType": "application/vnd.stellaops.report+json", - "payload": "eyJyZXBvcnRJZCI6InJlcG9ydC0zZGVmNWYzNjJhYTQ3NWVmMTRiNiIsImltYWdlRGlnZXN0Ijoic2hhMjU2OmRlYWRiZWVmIiwiZ2VuZXJhdGVkQXQiOiIyMDI1LTEwLTE5VDA4OjI4OjA5LjM2OTkyNjcrMDA6MDAiLCJ2ZXJkaWN0IjoiYmxvY2tlZCIsInBvbGljeSI6eyJyZXZpc2lvbklkIjoicmV2LTEiLCJkaWdlc3QiOiIyN2QyZWMyYjM0ZmVlZGMzMDRmYzU2NGQyNTJlY2VlMWM4ZmExNGVhNTgxYTVmZjVjMWVhODk2MzMxM2Q1YzhkIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwicnVsZU5hbWUiOiJCbG9jayBDcml0aWNhbCIsInJ1bGVBY3Rpb24iOiJCbG9jayIsInNjb3JlIjo0MC41LCJjb25maWdWZXJzaW9uIjoiMS4wIiwiaW5wdXRzIjp7InJlYWNoYWJpbGl0eVdlaWdodCI6MC40NSwiYmFzZVNjb3JlIjo0MC41LCJzZXZlcml0eVdlaWdodCI6OTAsInRydXN0V2VpZ2h0IjoxLCJ0cnVzdFdlaWdodC5OVkQiOjEsInJlYWNoYWJpbGl0eS5ydW50aW1lIjowLjQ1fSwicXVpZXQiOmZhbHNlLCJzb3VyY2VUcnVzdCI6Ik5WRCIsInJlYWNoYWJpbGl0eSI6InJ1bnRpbWUifV0sImlzc3VlcyI6W119", + "payload": "eyJyZXBvcnRJZCI6InJlcG9ydC0zZGVmNWYzNjJhYTQ3NWVmMTRiNiIsImltYWdlRGlnZXN0Ijoic2hhMjU2OmRlYWRiZWVmIiwiZ2VuZXJhdGVkQXQiOiIyMDI1LTEwLTE5VDA4OjI4OjA5LjM2OTkyNjcrMDA6MDAiLCJ2ZXJkaWN0IjoiYmxvY2tlZCIsInBvbGljeSI6eyJyZXZpc2lvbklkIjoicmV2LTEiLCJkaWdlc3QiOiIyN2QyZWMyYjM0ZmVlZGMzMDRmYzU2NGQyNTJlY2VlMWM4ZmExNGVhNTgxYTVmZjVjMWVhODk2MzMxM2Q1YzhkIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MX0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwicnVsZU5hbWUiOiJCbG9jayBDcml0aWNhbCIsInJ1bGVBY3Rpb24iOiJCbG9jayIsInNjb3JlIjo0MC41LCJjb25maWdWZXJzaW9uIjoiMS4wIiwiaW5wdXRzIjp7InJlYWNoYWJpbGl0eVdlaWdodCI6MC40NSwiYmFzZVNjb3JlIjo0MC41LCJzZXZlcml0eVdlaWdodCI6OTAsInRydXN0V2VpZ2h0IjoxLCJ0cnVzdFdlaWdodC5OVkQiOjEsInJlYWNoYWJpbGl0eS5ydW50aW1lIjowLjQ1LCJ1bmtub3duQ29uZmlkZW5jZSI6MC41MiwidW5rbm93bkFnZURheXMiOjR9LCJxdWlldGVkQnkiOiJwb2xpY3kvcXVpZXQtY3JpdGljYWwtcnVudGltZSIsInF1aWV0Ijp0cnVlLCJ1bmtub3duQ29uZmlkZW5jZSI6MC41MiwiY29uZmlkZW5jZUJhbmQiOiJtZWRpdW0iLCJ1bmtub3duQWdlRGF5cyI6NCwic291cmNlVHJ1c3QiOiJOVkQiLCJyZWFjaGFiaWxpdHkiOiJydW50aW1lIn1dLCJpc3N1ZXMiOltdfQ==", "signatures": [ { "keyId": "scanner-report-signing", diff --git a/src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md b/src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md index 1e9540c6..759ef04c 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md +++ b/src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md @@ -6,5 +6,5 @@ | 2 | SCANNER-ANALYZERS-LANG-10-305B | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-305A | Extract assembly metadata (strong name, file/product info) and optional Authenticode details when offline cert bundle provided. | Signing metadata captured for signed assemblies; offline trust store documented; hash validations deterministic. | | 3 | SCANNER-ANALYZERS-LANG-10-305C | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-305B | Handle self-contained apps and native assets; merge with EntryTrace usage hints. | Self-contained fixtures map to components with RID flags; usage hints propagate; tests cover linux/win variants. | | 4 | SCANNER-ANALYZERS-LANG-10-307D | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-305C | Integrate shared helpers (license mapping, quiet provenance) and concurrency-safe caches. | Shared helpers reused; concurrency tests for parallel layer scans pass; no redundant allocations. | -| 5 | SCANNER-ANALYZERS-LANG-10-308D | TODO | SCANNER-ANALYZERS-LANG-10-307D | Determinism fixtures + benchmark harness; compare to competitor scanners for accuracy/perf. | Fixtures in `Fixtures/lang/dotnet/`; determinism CI guard; benchmark demonstrates lower duplication + faster runtime. | -| 6 | SCANNER-ANALYZERS-LANG-10-309D | TODO | SCANNER-ANALYZERS-LANG-10-308D | Package plug-in (manifest, DI registration) and update Offline Kit instructions. | Manifest copied to `plugins/scanner/analyzers/lang/`; Worker loads analyzer; Offline Kit doc updated. | +| 5 | SCANNER-ANALYZERS-LANG-10-308D | DONE (2025-10-23) | SCANNER-ANALYZERS-LANG-10-307D | Determinism fixtures + benchmark harness; compare to competitor scanners for accuracy/perf. | Fixtures in `Fixtures/lang/dotnet/`; determinism CI guard; benchmark demonstrates lower duplication + faster runtime. | +| 6 | SCANNER-ANALYZERS-LANG-10-309D | DONE (2025-10-23) | SCANNER-ANALYZERS-LANG-10-308D | Package plug-in (manifest, DI registration) and update Offline Kit instructions. | Manifest copied to `plugins/scanner/analyzers/lang/`; Worker loads analyzer; Offline Kit doc updated. | diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Go.Tests/Fixtures/lang/go/stripped/expected.json b/src/StellaOps.Scanner.Analyzers.Lang.Go.Tests/Fixtures/lang/go/stripped/expected.json index 815ffbd9..bdd1a771 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang.Go.Tests/Fixtures/lang/go/stripped/expected.json +++ b/src/StellaOps.Scanner.Analyzers.Lang.Go.Tests/Fixtures/lang/go/stripped/expected.json @@ -1,12 +1,12 @@ [ { "analyzerId": "golang", - "componentKey": "golang::bin::sha256:80f528c90b72a4c4cc3fa078501154e4f2a3f49faea3ec380112d61740bde4c3", + "componentKey": "golang::bin::sha256:7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99", "name": "app", "type": "bin", "usedByEntrypoint": false, "metadata": { - "binary.sha256": "80f528c90b72a4c4cc3fa078501154e4f2a3f49faea3ec380112d61740bde4c3", + "binary.sha256": "7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99", "binaryPath": "app", "go.version.hint": "go1.22.8", "languageHint": "golang", @@ -17,7 +17,7 @@ "kind": "file", "source": "binary", "locator": "app", - "sha256": "80f528c90b72a4c4cc3fa078501154e4f2a3f49faea3ec380112d61740bde4c3" + "sha256": "7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99" }, { "kind": "metadata", diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Go.Tests/Go/GoLanguageAnalyzerTests.cs b/src/StellaOps.Scanner.Analyzers.Lang.Go.Tests/Go/GoLanguageAnalyzerTests.cs index a87e8005..373f7cde 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang.Go.Tests/Go/GoLanguageAnalyzerTests.cs +++ b/src/StellaOps.Scanner.Analyzers.Lang.Go.Tests/Go/GoLanguageAnalyzerTests.cs @@ -1,4 +1,7 @@ +using System; +using System.Diagnostics.Metrics; using System.IO; +using System.Linq; using StellaOps.Scanner.Analyzers.Lang.Go; using StellaOps.Scanner.Analyzers.Lang.Tests.Harness; using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities; @@ -63,4 +66,69 @@ public sealed class GoLanguageAnalyzerTests analyzers, cancellationToken); } + + [Fact] + public async Task ParallelRunsRemainDeterministicAsync() + { + var cancellationToken = TestContext.Current.CancellationToken; + var fixturePath = TestPaths.ResolveFixture("lang", "go", "basic"); + var goldenPath = Path.Combine(fixturePath, "expected.json"); + + var analyzers = new ILanguageAnalyzer[] + { + new GoLanguageAnalyzer(), + }; + + var tasks = Enumerable + .Range(0, Environment.ProcessorCount) + .Select(_ => LanguageAnalyzerTestHarness.AssertDeterministicAsync( + fixturePath, + goldenPath, + analyzers, + cancellationToken)); + + await Task.WhenAll(tasks); + } + + [Fact] + public async Task HeuristicMetricCounterIncrementsAsync() + { + var cancellationToken = TestContext.Current.CancellationToken; + var fixturePath = TestPaths.ResolveFixture("lang", "go", "stripped"); + + var analyzers = new ILanguageAnalyzer[] + { + new GoLanguageAnalyzer(), + }; + + var total = 0L; + + using var listener = new MeterListener + { + InstrumentPublished = (instrument, meterListener) => + { + if (instrument.Meter.Name == "StellaOps.Scanner.Analyzers.Lang.Go" + && instrument.Name == "scanner_analyzer_golang_heuristic_total") + { + meterListener.EnableMeasurementEvents(instrument); + } + } + }; + + listener.SetMeasurementEventCallback((_, measurement, _, _) => + { + Interlocked.Add(ref total, measurement); + }); + + listener.Start(); + + await LanguageAnalyzerTestHarness.RunToJsonAsync( + fixturePath, + analyzers, + cancellationToken: cancellationToken).ConfigureAwait(false); + + listener.Dispose(); + + Assert.Equal(1, Interlocked.Read(ref total)); + } } diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Go/AGENTS.md b/src/StellaOps.Scanner.Analyzers.Lang.Go/AGENTS.md index 8abf0f95..c219287c 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang.Go/AGENTS.md +++ b/src/StellaOps.Scanner.Analyzers.Lang.Go/AGENTS.md @@ -15,15 +15,17 @@ Build the Go analyzer plug-in that reads Go build info, module metadata, and DWA - Policy decisions or vulnerability joins. ## Expectations -- Latency targets: ≤400 µs (hot) / ≤2 ms (cold) per binary; minimal allocations via buffer pooling. -- Deterministic fallback to `bin:{sha256}` when metadata absent; heuristics clearly identified. -- Offline-first: rely solely on embedded metadata. -- Telemetry for binaries processed, metadata coverage, heuristics usage. - -## Dependencies -- Shared language analyzer core; Worker dispatcher; caching infrastructure (layer cache + file CAS). +- Latency targets: ≤400 µs (hot) / ≤2 ms (cold) per binary; minimal allocations via buffer pooling. +- Shared buffer pooling via `ArrayPool` for build-info/DWARF reads; safe for concurrent scans. +- Deterministic fallback to `bin:{sha256}` when metadata absent; heuristics clearly identified. +- Offline-first: rely solely on embedded metadata. +- Telemetry for binaries processed, metadata coverage, heuristics usage. +- Heuristic fallback metrics: `scanner_analyzer_golang_heuristic_total{indicator,version_hint}` increments whenever stripped binaries are classified via fallbacks. + +## Dependencies +- Shared language analyzer core; Worker dispatcher; caching infrastructure (layer cache + file CAS). ## Testing & Artifacts -- Golden fixtures for modules with/without VCS info, stripped binaries, cross-compiled variants. -- Benchmark comparison with competitor scanners to demonstrate speed/fidelity advantages. -- ADR documenting heuristics and risk mitigation. +- Golden fixtures for modules with/without VCS info, stripped binaries, cross-compiled variants. +- Benchmark comparison with competitor scanners to demonstrate speed/fidelity advantages (captured in `bench/Scanner.Analyzers/lang/go/`). +- ADR documenting heuristics and risk mitigation. diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Go/GoLanguageAnalyzer.cs b/src/StellaOps.Scanner.Analyzers.Lang.Go/GoLanguageAnalyzer.cs index 41a6642a..16d9a26c 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang.Go/GoLanguageAnalyzer.cs +++ b/src/StellaOps.Scanner.Analyzers.Lang.Go/GoLanguageAnalyzer.cs @@ -233,6 +233,8 @@ public sealed class GoLanguageAnalyzer : ILanguageAnalyzer metadata: metadata, evidence: evidence, usedByEntrypoint: usedByEntrypoint); + + GoAnalyzerMetrics.RecordHeuristic(strippedBinary.Indicator, !string.IsNullOrEmpty(strippedBinary.GoVersionHint)); } private static IEnumerable BuildEvidence(GoBuildInfo buildInfo, GoModule module, string binaryRelativePath, LanguageAnalyzerContext context, ref string? binaryHash) diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoAnalyzerMetrics.cs b/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoAnalyzerMetrics.cs new file mode 100644 index 00000000..801c1d6a --- /dev/null +++ b/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoAnalyzerMetrics.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Diagnostics.Metrics; + +namespace StellaOps.Scanner.Analyzers.Lang.Go.Internal; + +internal static class GoAnalyzerMetrics +{ + private static readonly Meter Meter = new("StellaOps.Scanner.Analyzers.Lang.Go", "1.0.0"); + + private static readonly Counter HeuristicCounter = Meter.CreateCounter( + "scanner_analyzer_golang_heuristic_total", + unit: "components", + description: "Counts Go components emitted via heuristic fallbacks when build metadata is missing."); + + public static void RecordHeuristic(GoStrippedBinaryIndicator indicator, bool hasVersionHint) + { + HeuristicCounter.Add( + 1, + new KeyValuePair("indicator", NormalizeIndicator(indicator)), + new KeyValuePair("version_hint", hasVersionHint ? "present" : "absent")); + } + + private static string NormalizeIndicator(GoStrippedBinaryIndicator indicator) + => indicator switch + { + GoStrippedBinaryIndicator.BuildId => "build-id", + GoStrippedBinaryIndicator.GoRuntimeMarkers => "runtime-markers", + _ => "unknown", + }; +} diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoBinaryScanner.cs b/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoBinaryScanner.cs index 8676c3a4..cc1f5774 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoBinaryScanner.cs +++ b/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoBinaryScanner.cs @@ -38,16 +38,59 @@ internal static class GoBinaryScanner goVersion = null; moduleData = null; + FileInfo info; try { - var info = new FileInfo(filePath); + info = new FileInfo(filePath); if (!info.Exists || info.Length < 64 || info.Length > 128 * 1024 * 1024) { return false; } + } + catch (IOException) + { + return false; + } + catch (UnauthorizedAccessException) + { + return false; + } + catch (System.Security.SecurityException) + { + return false; + } - var data = File.ReadAllBytes(filePath); - var span = new ReadOnlySpan(data); + var length = info.Length; + if (length <= 0) + { + return false; + } + + var inspectLength = (int)Math.Min(length, int.MaxValue); + var buffer = ArrayPool.Shared.Rent(inspectLength); + + try + { + using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + var totalRead = 0; + + while (totalRead < inspectLength) + { + var read = stream.Read(buffer, totalRead, inspectLength - totalRead); + if (read <= 0) + { + break; + } + + totalRead += read; + } + + if (totalRead < 64) + { + return false; + } + + var span = new ReadOnlySpan(buffer, 0, totalRead); var offset = span.IndexOf(BuildInfoMagic.Span); if (offset < 0) { @@ -65,6 +108,11 @@ internal static class GoBinaryScanner { return false; } + finally + { + Array.Clear(buffer, 0, inspectLength); + ArrayPool.Shared.Return(buffer); + } } public static bool TryClassifyStrippedBinary(string filePath, out GoStrippedBinaryClassification classification) diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoDwarfReader.cs b/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoDwarfReader.cs index 96b8f1bf..e1fa5f76 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoDwarfReader.cs +++ b/src/StellaOps.Scanner.Analyzers.Lang.Go/Internal/GoDwarfReader.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.IO; using System.Text; @@ -15,16 +16,10 @@ internal static class GoDwarfReader { metadata = null; - ReadOnlySpan data; + FileInfo fileInfo; try { - var fileInfo = new FileInfo(path); - if (!fileInfo.Exists || fileInfo.Length == 0 || fileInfo.Length > 256 * 1024 * 1024) - { - return false; - } - - data = File.ReadAllBytes(path); + fileInfo = new FileInfo(path); } catch (IOException) { @@ -35,27 +30,62 @@ internal static class GoDwarfReader return false; } - var revision = ExtractValue(data, VcsRevisionToken); - var modifiedText = ExtractValue(data, VcsModifiedToken); - var timestamp = ExtractValue(data, VcsTimeToken); - var system = ExtractValue(data, VcsSystemToken); - - bool? modified = null; - if (!string.IsNullOrWhiteSpace(modifiedText)) - { - if (bool.TryParse(modifiedText, out var parsed)) - { - modified = parsed; - } - } - - if (string.IsNullOrWhiteSpace(revision) && string.IsNullOrWhiteSpace(system) && modified is null && string.IsNullOrWhiteSpace(timestamp)) + if (!fileInfo.Exists || fileInfo.Length == 0 || fileInfo.Length > 256 * 1024 * 1024) { return false; } - metadata = new GoDwarfMetadata(system, revision, modified, timestamp); - return true; + var length = fileInfo.Length; + var readLength = (int)Math.Min(length, int.MaxValue); + var buffer = ArrayPool.Shared.Rent(readLength); + var bytesRead = 0; + + try + { + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + bytesRead = stream.Read(buffer, 0, readLength); + if (bytesRead <= 0) + { + return false; + } + + var data = new ReadOnlySpan(buffer, 0, bytesRead); + + var revision = ExtractValue(data, VcsRevisionToken); + var modifiedText = ExtractValue(data, VcsModifiedToken); + var timestamp = ExtractValue(data, VcsTimeToken); + var system = ExtractValue(data, VcsSystemToken); + + bool? modified = null; + if (!string.IsNullOrWhiteSpace(modifiedText)) + { + if (bool.TryParse(modifiedText, out var parsed)) + { + modified = parsed; + } + } + + if (string.IsNullOrWhiteSpace(revision) && string.IsNullOrWhiteSpace(system) && modified is null && string.IsNullOrWhiteSpace(timestamp)) + { + return false; + } + + metadata = new GoDwarfMetadata(system, revision, modified, timestamp); + return true; + } + catch (IOException) + { + return false; + } + catch (UnauthorizedAccessException) + { + return false; + } + finally + { + Array.Clear(buffer, 0, bytesRead); + ArrayPool.Shared.Return(buffer); + } } private static string? ExtractValue(ReadOnlySpan data, ReadOnlySpan token) diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md b/src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md index 5e1490d3..8e1a38e3 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md +++ b/src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md @@ -5,7 +5,8 @@ | 1 | SCANNER-ANALYZERS-LANG-10-304A | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-307 | Parse Go build info blob (`runtime/debug` format) and `.note.go.buildid`; map to module/version and evidence. | Build info extracted across Go 1.18–1.23 fixtures; evidence includes VCS, module path, and build settings. | | 2 | SCANNER-ANALYZERS-LANG-10-304B | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-304A | Implement DWARF-lite reader for VCS metadata + dirty flag; add cache to avoid re-reading identical binaries. | DWARF reader supplies commit hash for ≥95 % fixtures; cache reduces duplicated IO by ≥70 %. | | 3 | SCANNER-ANALYZERS-LANG-10-304C | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-304B | Fallback heuristics for stripped binaries with deterministic `bin:{sha256}` labeling and quiet provenance. | Heuristic labels clearly separated; tests ensure no false “observed” provenance; documentation updated. | -| 4 | SCANNER-ANALYZERS-LANG-10-307G | TODO | SCANNER-ANALYZERS-LANG-10-304C | Wire shared helpers (license mapping, usage flags) and ensure concurrency-safe buffer reuse. | Analyzer reuses shared infrastructure; concurrency tests with parallel scans pass; no data races. | -| 5 | SCANNER-ANALYZERS-LANG-10-308G | TODO | SCANNER-ANALYZERS-LANG-10-307G | Determinism fixtures + benchmark harness (Vs competitor). | Fixtures under `Fixtures/lang/go/`; CI determinism check; benchmark runs showing ≥20 % speed advantage. | -| 6 | SCANNER-ANALYZERS-LANG-10-309G | TODO | SCANNER-ANALYZERS-LANG-10-308G | Package plug-in manifest + Offline Kit notes; ensure Worker DI registration. | Manifest copied; Worker loads analyzer; Offline Kit docs updated with Go analyzer presence. | -| 7 | SCANNER-ANALYZERS-LANG-10-304D | TODO | SCANNER-ANALYZERS-LANG-10-304C | Emit telemetry counters for stripped-binary heuristics and document metrics wiring. | New `scanner_analyzer_golang_heuristic_total` counter recorded; docs updated with offline aggregation notes. | +| 4 | SCANNER-ANALYZERS-LANG-10-307G | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-304C | Wire shared helpers (license mapping, usage flags) and ensure concurrency-safe buffer reuse. | Analyzer reuses shared infrastructure; concurrency tests with parallel scans pass; no data races. | +| 5 | SCANNER-ANALYZERS-LANG-10-308G | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-307G | Determinism fixtures + benchmark harness (Vs competitor). | Fixtures under `Fixtures/lang/go/`; CI determinism check; benchmark runs showing ≥20 % speed advantage. | +| 6 | SCANNER-ANALYZERS-LANG-10-309G | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-308G | Package plug-in manifest + Offline Kit notes; ensure Worker DI registration. | Manifest copied; Worker loads analyzer; Offline Kit docs updated with Go analyzer presence. | +| 7 | SCANNER-ANALYZERS-LANG-10-304D | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-304C | Emit telemetry counters for stripped-binary heuristics and document metrics wiring. | New `scanner_analyzer_golang_heuristic_total` counter recorded; docs updated with offline aggregation notes. | +| 8 | SCANNER-ANALYZERS-LANG-10-304E | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-304D | Plumb Go heuristic counter into Scanner metrics pipeline and alerting. | Counter emitted through Worker telemetry/export pipeline; dashboard & alert rule documented; smoke test proves metric visibility. | diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Python/TASKS.md b/src/StellaOps.Scanner.Analyzers.Lang.Python/TASKS.md index a4ecd1e7..0144765d 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang.Python/TASKS.md +++ b/src/StellaOps.Scanner.Analyzers.Lang.Python/TASKS.md @@ -5,6 +5,6 @@ | 1 | SCANNER-ANALYZERS-LANG-10-303A | DONE (2025-10-21) | SCANNER-ANALYZERS-LANG-10-307 | STREAM-based parser for `*.dist-info` (`METADATA`, `WHEEL`, `entry_points.txt`) with normalization + evidence capture. | Parser handles CPython 3.8–3.12 metadata variations; fixtures confirm canonical ordering and UTF-8 handling. | | 2 | SCANNER-ANALYZERS-LANG-10-303B | DONE (2025-10-21) | SCANNER-ANALYZERS-LANG-10-303A | RECORD hash verifier with chunked hashing, Zip64 support, and mismatch diagnostics. | Verifier processes 5 GB RECORD fixture without allocations >2 MB; mismatches produce deterministic evidence records. | | 3 | SCANNER-ANALYZERS-LANG-10-303C | DONE (2025-10-21) | SCANNER-ANALYZERS-LANG-10-303B | Editable install + pip cache detection; integrate EntryTrace hints for runtime usage flags. | Editable installs resolved to source path; usage flags propagated; regression tests cover mixed editable + wheel installs. | -| 4 | SCANNER-ANALYZERS-LANG-10-307P | TODO | SCANNER-ANALYZERS-LANG-10-303C | Shared helper integration (license metadata, quiet provenance, component merging). | Shared helpers reused; analyzer-specific metadata minimal; deterministic merge tests pass. | +| 4 | SCANNER-ANALYZERS-LANG-10-307P | DOING (2025-10-23) | SCANNER-ANALYZERS-LANG-10-303C | Shared helper integration (license metadata, quiet provenance, component merging). | Shared helpers reused; analyzer-specific metadata minimal; deterministic merge tests pass. | | 5 | SCANNER-ANALYZERS-LANG-10-308P | TODO | SCANNER-ANALYZERS-LANG-10-307P | Golden fixtures + determinism harness for Python analyzer; add benchmark and hash throughput reporting. | Fixtures under `Fixtures/lang/python/`; determinism CI guard; benchmark CSV added with threshold alerts. | | 6 | SCANNER-ANALYZERS-LANG-10-309P | TODO | SCANNER-ANALYZERS-LANG-10-308P | Package plug-in (manifest, DI registration) and document Offline Kit bundling of Python stdlib metadata if needed. | Manifest copied to `plugins/scanner/analyzers/lang/`; Worker loads analyzer; Offline Kit doc updated. | diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Tests/DotNet/DotNetLanguageAnalyzerTests.cs b/src/StellaOps.Scanner.Analyzers.Lang.Tests/DotNet/DotNetLanguageAnalyzerTests.cs index 722b7675..9a9416a0 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang.Tests/DotNet/DotNetLanguageAnalyzerTests.cs +++ b/src/StellaOps.Scanner.Analyzers.Lang.Tests/DotNet/DotNetLanguageAnalyzerTests.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using StellaOps.Scanner.Analyzers.Lang.DotNet; @@ -102,6 +103,54 @@ public sealed class DotNetLanguageAnalyzerTests Assert.Equal(first, result); } } + + [Fact] + public async Task MultiFixtureMergesRuntimeMetadataAsync() + { + var cancellationToken = TestContext.Current.CancellationToken; + var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "multi"); + var goldenPath = Path.Combine(fixturePath, "expected.json"); + + var analyzers = new ILanguageAnalyzer[] + { + new DotNetLanguageAnalyzer() + }; + + await LanguageAnalyzerTestHarness.AssertDeterministicAsync( + fixturePath, + goldenPath, + analyzers, + cancellationToken); + + var json = await LanguageAnalyzerTestHarness.RunToJsonAsync( + fixturePath, + analyzers, + cancellationToken); + + using var document = JsonDocument.Parse(json); + var root = document.RootElement; + Assert.True(root.ValueKind == JsonValueKind.Array, "Result root should be an array."); + Assert.Equal(2, root.GetArrayLength()); + + var loggingComponent = root.EnumerateArray() + .First(element => element.GetProperty("name").GetString() == "StellaOps.Logging"); + + var metadata = loggingComponent.GetProperty("metadata"); + Assert.Equal("StellaOps.Logging", loggingComponent.GetProperty("name").GetString()); + Assert.Equal("2.5.1", loggingComponent.GetProperty("version").GetString()); + Assert.Equal("pkg:nuget/stellaops.logging@2.5.1", loggingComponent.GetProperty("purl").GetString()); + + var ridValues = metadata.EnumerateObject() + .Where(property => property.Name.Contains(".rid", StringComparison.Ordinal)) + .Select(property => property.Value.GetString()) + .Where(value => !string.IsNullOrEmpty(value)) + .Select(value => value!) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + Assert.Contains("linux-x64", ridValues); + Assert.Contains("osx-arm64", ridValues); + Assert.Contains("win-arm64", ridValues); + } private sealed class StubAuthenticodeInspector : IDotNetAuthenticodeInspector { diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppA.deps.json b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppA.deps.json new file mode 100644 index 00000000..b5412459 --- /dev/null +++ b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppA.deps.json @@ -0,0 +1,84 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0/osx-arm64" + }, + "targets": { + ".NETCoreApp,Version=v10.0": { + "AppA/2.0.0": { + "dependencies": { + "StellaOps.Toolkit": "1.2.3", + "StellaOps.Logging": "2.5.1" + } + }, + "StellaOps.Toolkit/1.2.3": { + "dependencies": { + "StellaOps.Logging": "2.5.1" + }, + "runtime": { + "lib/net10.0/StellaOps.Toolkit.dll": { + "assemblyVersion": "1.2.3.0", + "fileVersion": "1.2.3.0" + } + } + }, + "StellaOps.Logging/2.5.1": { + "runtime": { + "lib/net10.0/StellaOps.Logging.dll": { + "assemblyVersion": "2.5.1.0", + "fileVersion": "2.5.1.12345" + } + } + } + }, + ".NETCoreApp,Version=v10.0/linux-x64": { + "StellaOps.Toolkit/1.2.3": { + "runtimeTargets": { + "runtimes/linux-x64/native/libstellaops.toolkit.so": { + "rid": "linux-x64", + "assetType": "native" + } + } + }, + "StellaOps.Logging/2.5.1": { + "runtime": { + "runtimes/linux-x64/lib/net10.0/StellaOps.Logging.dll": {} + } + } + }, + ".NETCoreApp,Version=v10.0/osx-arm64": { + "StellaOps.Toolkit/1.2.3": { + "runtimeTargets": { + "runtimes/osx-arm64/native/libstellaops.toolkit.dylib": { + "rid": "osx-arm64", + "assetType": "native" + } + } + }, + "StellaOps.Logging/2.5.1": { + "runtime": { + "runtimes/osx-arm64/lib/net10.0/StellaOps.Logging.dll": {} + } + } + } + }, + "libraries": { + "AppA/2.0.0": { + "type": "project", + "serviceable": false + }, + "StellaOps.Toolkit/1.2.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-FAKE_TOOLKIT_SHA==", + "path": "stellaops.toolkit/1.2.3", + "hashPath": "stellaops.toolkit.1.2.3.nupkg.sha512" + }, + "StellaOps.Logging/2.5.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-FAKE_LOGGING_SHA==", + "path": "stellaops.logging/2.5.1", + "hashPath": "stellaops.logging.2.5.1.nupkg.sha512" + } + } +} diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppA.runtimeconfig.json b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppA.runtimeconfig.json new file mode 100644 index 00000000..36677e13 --- /dev/null +++ b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppA.runtimeconfig.json @@ -0,0 +1,39 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.1" + }, + "frameworks": [ + { + "name": "Microsoft.NETCore.App", + "version": "10.0.1" + }, + { + "name": "Microsoft.AspNetCore.App", + "version": "10.0.0" + }, + { + "name": "StellaOps.Hosting", + "version": "2.0.0" + } + ], + "runtimeGraph": { + "runtimes": { + "osx-arm64": { + "fallbacks": [ + "osx", + "unix" + ] + }, + "linux-x64": { + "fallbacks": [ + "linux", + "unix" + ] + } + } + } + } +} diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppB.deps.json b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppB.deps.json new file mode 100644 index 00000000..0a5b09f1 --- /dev/null +++ b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppB.deps.json @@ -0,0 +1,76 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0/win-arm64" + }, + "targets": { + ".NETCoreApp,Version=v10.0": { + "AppB/3.1.0": { + "dependencies": { + "StellaOps.Toolkit": "1.2.3", + "StellaOps.Logging": "2.5.1" + } + }, + "StellaOps.Toolkit/1.2.3": { + "runtime": { + "lib/net10.0/StellaOps.Toolkit.dll": { + "assemblyVersion": "1.2.3.0", + "fileVersion": "1.2.3.0" + } + } + }, + "StellaOps.Logging/2.5.1": { + "runtime": { + "lib/net10.0/StellaOps.Logging.dll": { + "assemblyVersion": "2.5.1.0", + "fileVersion": "2.5.1.12345" + } + } + } + }, + ".NETCoreApp,Version=v10.0/win-arm64": { + "StellaOps.Toolkit/1.2.3": { + "runtimeTargets": { + "runtimes/win-arm64/native/stellaops.toolkit.dll": { + "rid": "win-arm64", + "assetType": "native" + } + } + }, + "StellaOps.Logging/2.5.1": { + "runtimeTargets": { + "runtimes/win-arm64/native/stellaops.logging.dll": { + "rid": "win-arm64", + "assetType": "native" + } + } + } + }, + ".NETCoreApp,Version=v10.0/linux-arm64": { + "StellaOps.Logging/2.5.1": { + "runtime": { + "runtimes/linux-arm64/lib/net10.0/StellaOps.Logging.dll": {} + } + } + } + }, + "libraries": { + "AppB/3.1.0": { + "type": "project", + "serviceable": false + }, + "StellaOps.Toolkit/1.2.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-FAKE_TOOLKIT_SHA==", + "path": "stellaops.toolkit/1.2.3", + "hashPath": "stellaops.toolkit.1.2.3.nupkg.sha512" + }, + "StellaOps.Logging/2.5.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-FAKE_LOGGING_SHA==", + "path": "stellaops.logging/2.5.1", + "hashPath": "stellaops.logging.2.5.1.nupkg.sha512" + } + } +} diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppB.runtimeconfig.json b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppB.runtimeconfig.json new file mode 100644 index 00000000..049280f8 --- /dev/null +++ b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/AppB.runtimeconfig.json @@ -0,0 +1,38 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + }, + "frameworks": [ + { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + }, + { + "name": "Microsoft.WindowsDesktop.App", + "version": "10.0.0" + } + ], + "additionalProbingPaths": [ + "C:/Users/runner/.nuget/packages" + ], + "runtimeGraph": { + "runtimes": { + "win-arm64": { + "fallbacks": [ + "win", + "any" + ] + }, + "linux-arm64": { + "fallbacks": [ + "linux", + "unix" + ] + } + } + } + } +} diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/expected.json b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/expected.json new file mode 100644 index 00000000..181578b8 --- /dev/null +++ b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/expected.json @@ -0,0 +1,120 @@ +[ + { + "analyzerId": "dotnet", + "componentKey": "purl::pkg:nuget/stellaops.logging@2.5.1", + "purl": "pkg:nuget/stellaops.logging@2.5.1", + "name": "StellaOps.Logging", + "version": "2.5.1", + "type": "nuget", + "usedByEntrypoint": false, + "metadata": { + "assembly[0].assetPath": "lib/net10.0/StellaOps.Logging.dll", + "assembly[0].fileVersion": "2.5.1.12345", + "assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0", + "assembly[0].version": "2.5.1.0", + "assembly[1].assetPath": "runtimes/linux-arm64/lib/net10.0/StellaOps.Logging.dll", + "assembly[1].rid[0]": "linux-arm64", + "assembly[1].tfm[0]": ".NETCoreApp,Version=v10.0", + "assembly[2].assetPath": "runtimes/linux-x64/lib/net10.0/StellaOps.Logging.dll", + "assembly[2].rid[0]": "linux-x64", + "assembly[2].tfm[0]": ".NETCoreApp,Version=v10.0", + "assembly[3].assetPath": "runtimes/osx-arm64/lib/net10.0/StellaOps.Logging.dll", + "assembly[3].rid[0]": "osx-arm64", + "assembly[3].tfm[0]": ".NETCoreApp,Version=v10.0", + "deps.path[0]": "AppA.deps.json", + "deps.path[1]": "AppB.deps.json", + "deps.rid[0]": "linux-arm64", + "deps.rid[1]": "linux-x64", + "deps.rid[2]": "osx-arm64", + "deps.rid[3]": "win-arm64", + "deps.tfm[0]": ".NETCoreApp,Version=v10.0", + "license.expression[0]": "Apache-2.0", + "native[0].assetPath": "runtimes/win-arm64/native/stellaops.logging.dll", + "native[0].rid[0]": "win-arm64", + "native[0].tfm[0]": ".NETCoreApp,Version=v10.0", + "package.hashPath[0]": "stellaops.logging.2.5.1.nupkg.sha512", + "package.id": "StellaOps.Logging", + "package.id.normalized": "stellaops.logging", + "package.path[0]": "stellaops.logging/2.5.1", + "package.serviceable": "true", + "package.sha512[0]": "sha512-FAKE_LOGGING_SHA==", + "package.version": "2.5.1", + "provenance": "manifest" + }, + "evidence": [ + { + "kind": "file", + "source": "deps.json", + "locator": "AppA.deps.json", + "value": "StellaOps.Logging/2.5.1" + }, + { + "kind": "file", + "source": "deps.json", + "locator": "AppB.deps.json", + "value": "StellaOps.Logging/2.5.1" + } + ] + }, + { + "analyzerId": "dotnet", + "componentKey": "purl::pkg:nuget/stellaops.toolkit@1.2.3", + "purl": "pkg:nuget/stellaops.toolkit@1.2.3", + "name": "StellaOps.Toolkit", + "version": "1.2.3", + "type": "nuget", + "usedByEntrypoint": false, + "metadata": { + "assembly[0].assetPath": "lib/net10.0/StellaOps.Toolkit.dll", + "assembly[0].fileVersion": "1.2.3.0", + "assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0", + "assembly[0].version": "1.2.3.0", + "deps.dependency[0]": "stellaops.logging", + "deps.path[0]": "AppA.deps.json", + "deps.path[1]": "AppB.deps.json", + "deps.rid[0]": "linux-x64", + "deps.rid[1]": "osx-arm64", + "deps.rid[2]": "win-arm64", + "deps.tfm[0]": ".NETCoreApp,Version=v10.0", + "license.file.sha256[0]": "604e182900b0ecb1ffb911c817bcbd148a31b8f55ad392a3b770be8005048c5c", + "license.file[0]": "packages/stellaops.toolkit/1.2.3/LICENSE.txt", + "native[0].assetPath": "runtimes/linux-x64/native/libstellaops.toolkit.so", + "native[0].rid[0]": "linux-x64", + "native[0].tfm[0]": ".NETCoreApp,Version=v10.0", + "native[1].assetPath": "runtimes/osx-arm64/native/libstellaops.toolkit.dylib", + "native[1].rid[0]": "osx-arm64", + "native[1].tfm[0]": ".NETCoreApp,Version=v10.0", + "native[2].assetPath": "runtimes/win-arm64/native/stellaops.toolkit.dll", + "native[2].rid[0]": "win-arm64", + "native[2].tfm[0]": ".NETCoreApp,Version=v10.0", + "package.hashPath[0]": "stellaops.toolkit.1.2.3.nupkg.sha512", + "package.id": "StellaOps.Toolkit", + "package.id.normalized": "stellaops.toolkit", + "package.path[0]": "stellaops.toolkit/1.2.3", + "package.serviceable": "true", + "package.sha512[0]": "sha512-FAKE_TOOLKIT_SHA==", + "package.version": "1.2.3", + "provenance": "manifest" + }, + "evidence": [ + { + "kind": "file", + "source": "deps.json", + "locator": "AppA.deps.json", + "value": "StellaOps.Toolkit/1.2.3" + }, + { + "kind": "file", + "source": "deps.json", + "locator": "AppB.deps.json", + "value": "StellaOps.Toolkit/1.2.3" + }, + { + "kind": "file", + "source": "license", + "locator": "packages/stellaops.toolkit/1.2.3/LICENSE.txt", + "sha256": "604e182900b0ecb1ffb911c817bcbd148a31b8f55ad392a3b770be8005048c5c" + } + ] + } +] \ No newline at end of file diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.logging/2.5.1/LICENSE.txt b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.logging/2.5.1/LICENSE.txt new file mode 100644 index 00000000..a2662911 --- /dev/null +++ b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.logging/2.5.1/LICENSE.txt @@ -0,0 +1,15 @@ +StellaOps Logging + +Copyright (c) 2025 StellaOps. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.logging/2.5.1/stellaops.logging.nuspec b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.logging/2.5.1/stellaops.logging.nuspec new file mode 100644 index 00000000..e8fe924b --- /dev/null +++ b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.logging/2.5.1/stellaops.logging.nuspec @@ -0,0 +1,12 @@ + + + + StellaOps.Logging + 2.5.1 + StellaOps + Logging sample package for analyzer fixtures. + Apache-2.0 + https://stella-ops.example/licenses/logging + https://stella-ops.example/projects/logging + + diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.toolkit/1.2.3/LICENSE.txt b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.toolkit/1.2.3/LICENSE.txt new file mode 100644 index 00000000..0b18126c --- /dev/null +++ b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.toolkit/1.2.3/LICENSE.txt @@ -0,0 +1,7 @@ +StellaOps Toolkit License +========================= + +This sample license is provided for test fixtures only. + +Permission is granted to use, copy, modify, and distribute this fixture +for the purpose of automated testing. diff --git a/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.toolkit/1.2.3/stellaops.toolkit.nuspec b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.toolkit/1.2.3/stellaops.toolkit.nuspec new file mode 100644 index 00000000..0889b15f --- /dev/null +++ b/src/StellaOps.Scanner.Analyzers.Lang.Tests/Fixtures/lang/dotnet/multi/packages/stellaops.toolkit/1.2.3/stellaops.toolkit.nuspec @@ -0,0 +1,11 @@ + + + + StellaOps.Toolkit + 1.2.3 + StellaOps + Toolkit sample package for analyzer fixtures. + LICENSE.txt + https://stella-ops.example/licenses/toolkit + + diff --git a/src/StellaOps.Scanner.Analyzers.Lang/SPRINTS_LANG_IMPLEMENTATION_PLAN.md b/src/StellaOps.Scanner.Analyzers.Lang/SPRINTS_LANG_IMPLEMENTATION_PLAN.md index 1327d2a9..3d325471 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang/SPRINTS_LANG_IMPLEMENTATION_PLAN.md +++ b/src/StellaOps.Scanner.Analyzers.Lang/SPRINTS_LANG_IMPLEMENTATION_PLAN.md @@ -68,6 +68,7 @@ All sprints below assume prerequisites from SP10-G2 (core scaffolding + Java ana - Tests verifying dual runtimeconfig merge logic. - Guidance for Policy on license propagation from NuGet metadata. - **Progress (2025-10-22):** Completed task 10-305A with a deterministic deps/runtimeconfig ingest pipeline producing `pkg:nuget` components across RID targets. Added dotnet fixture + golden output to the shared harness, wired analyzer plugin availability, and surfaced RID metadata in component records for downstream emit/diff work. License provenance and quiet flagging now ride through the shared helpers (task 10-307D), including nuspec license expression/file ingestion, manifest provenance tagging, and concurrency-safe file metadata caching with new parallel tests. +- **Progress (2025-10-23):** Landed determinism + benchmark coverage (task 10-308D) via the new `multi` fixture, golden outputs, and bench scenario wired into `baseline.csv`, plus Syft comparison data. Packaged the .NET plug-in for restart-only distribution (task 10-309D), verified manifest copy into `plugins/scanner/analyzers/lang/`, and refreshed `docs/24_OFFLINE_KIT.md` with updated Offline Kit instructions. ## Sprint LA5 — Rust Analyzer & Binary Fingerprinting (Tasks 10-306, 10-307, 10-308, 10-309 subset) - **Scope:** Detect crates via metadata in `.fingerprint`, Cargo.lock fragments, or embedded `rustc` markers; robust fallback to binary hash classification. diff --git a/src/StellaOps.Scanner.Analyzers.Lang/TASKS.md b/src/StellaOps.Scanner.Analyzers.Lang/TASKS.md index 8c6e75b0..e53f4d52 100644 --- a/src/StellaOps.Scanner.Analyzers.Lang/TASKS.md +++ b/src/StellaOps.Scanner.Analyzers.Lang/TASKS.md @@ -5,7 +5,7 @@ | SCANNER-ANALYZERS-LANG-10-301 | DONE (2025-10-19) | Language Analyzer Guild | SCANNER-CORE-09-501, SCANNER-WORKER-09-203 | Java analyzer emitting deterministic `pkg:maven` components using pom.properties / MANIFEST evidence. | Java analyzer extracts coordinates+version+licenses with provenance; golden fixtures deterministic; microbenchmark meets target. | | SCANNER-ANALYZERS-LANG-10-302 | DONE (2025-10-21) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-307 | Node analyzer resolving workspaces/symlinks into `pkg:npm` identities. | Node analyzer handles symlinks/workspaces; outputs sorted components; determinism harness covers hoisted deps. | | SCANNER-ANALYZERS-LANG-10-303 | DONE (2025-10-21) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-307 | Python analyzer consuming `*.dist-info` metadata and RECORD hashes. | Analyzer binds METADATA + RECORD evidence, includes entry points, determinism fixtures stable. | -| SCANNER-ANALYZERS-LANG-10-304 | DOING (2025-10-22) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-307 | Go analyzer leveraging buildinfo for `pkg:golang` components. | Buildinfo parser emits module path/version + vcs metadata; binaries without buildinfo downgraded gracefully. | +| SCANNER-ANALYZERS-LANG-10-304 | DONE (2025-10-22) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-307 | Go analyzer leveraging buildinfo for `pkg:golang` components. | Buildinfo parser emits module path/version + vcs metadata; binaries without buildinfo downgraded gracefully. | | SCANNER-ANALYZERS-LANG-10-305 | DONE (2025-10-22) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-307 | .NET analyzer parsing `*.deps.json`, assembly metadata, and RID variants. | Analyzer merges deps.json + assembly info; dedupes per RID; determinism verified. | | SCANNER-ANALYZERS-LANG-10-306 | DONE (2025-10-22) | Language Analyzer Guild | SCANNER-ANALYZERS-LANG-10-307 | Rust analyzer detecting crate provenance or falling back to `bin:{sha256}`. | Analyzer emits `pkg:cargo` when metadata present; falls back to binary hash; fixtures cover both paths. | | SCANNER-ANALYZERS-LANG-10-307 | DONE (2025-10-19) | Language Analyzer Guild | SCANNER-CORE-09-501 | Shared language evidence helpers + usage flag propagation. | Shared abstractions implemented; analyzers reuse helpers; evidence includes usage hints; unit tests cover canonical ordering. | diff --git a/src/StellaOps.Scanner.Emit.Tests/Composition/CycloneDxComposerTests.cs b/src/StellaOps.Scanner.Emit.Tests/Composition/CycloneDxComposerTests.cs index 4f325250..460d1c42 100644 --- a/src/StellaOps.Scanner.Emit.Tests/Composition/CycloneDxComposerTests.cs +++ b/src/StellaOps.Scanner.Emit.Tests/Composition/CycloneDxComposerTests.cs @@ -21,17 +21,46 @@ public sealed class CycloneDxComposerTests Assert.NotNull(result.Inventory); Assert.StartsWith("urn:uuid:", result.Inventory.SerialNumber, StringComparison.Ordinal); - Assert.Equal("application/vnd.cyclonedx+json; version=1.5", result.Inventory.JsonMediaType); - Assert.Equal("application/vnd.cyclonedx+protobuf; version=1.5", result.Inventory.ProtobufMediaType); - Assert.Equal(2, result.Inventory.Components.Length); - - Assert.NotNull(result.Usage); - Assert.Equal("application/vnd.cyclonedx+json; version=1.5; view=usage", result.Usage!.JsonMediaType); - Assert.Single(result.Usage.Components); - Assert.Equal("pkg:npm/a", result.Usage.Components[0].Identity.Key); - - ValidateJson(result.Inventory.JsonBytes, expectedComponentCount: 2, expectedView: "inventory"); - ValidateJson(result.Usage.JsonBytes, expectedComponentCount: 1, expectedView: "usage"); + Assert.Equal("application/vnd.cyclonedx+json; version=1.6", result.Inventory.JsonMediaType); + Assert.Equal("application/vnd.cyclonedx+protobuf; version=1.6", result.Inventory.ProtobufMediaType); + Assert.Equal(2, result.Inventory.Components.Length); + + Assert.NotNull(result.Usage); + Assert.Equal("application/vnd.cyclonedx+json; version=1.6; view=usage", result.Usage!.JsonMediaType); + Assert.Single(result.Usage.Components); + Assert.Equal("pkg:npm/a", result.Usage.Components[0].Identity.Key); + + ValidateJson(result.Inventory.JsonBytes, expectedComponentCount: 2, expectedView: "inventory"); + ValidateJson(result.Usage.JsonBytes, expectedComponentCount: 1, expectedView: "usage"); + + using var inventoryDoc = JsonDocument.Parse(result.Inventory.JsonBytes); + var inventoryRoot = inventoryDoc.RootElement; + Assert.True(inventoryRoot.TryGetProperty("vulnerabilities", out var inventoryVulnerabilities)); + var inventoryVulns = inventoryVulnerabilities.EnumerateArray().ToArray(); + Assert.Equal(2, inventoryVulns.Length); + + var primaryVuln = inventoryVulns.Single(v => string.Equals(v.GetProperty("bom-ref").GetString(), "finding-a", StringComparison.Ordinal)); + var primaryProperties = primaryVuln.GetProperty("properties") + .EnumerateArray() + .ToDictionary( + element => element.GetProperty("name").GetString()!, + element => element.GetProperty("value").GetString()!, + StringComparer.Ordinal); + Assert.Equal("Blocked", primaryProperties["stellaops:policy.status"]); + Assert.Equal("true", primaryProperties["stellaops:policy.quiet"]); + Assert.Equal("40.5", primaryProperties["stellaops:policy.score"]); + Assert.Equal("medium", primaryProperties["stellaops:policy.confidenceBand"]); + Assert.Equal("runtime", primaryProperties["stellaops:policy.reachability"]); + Assert.Equal("0.45", primaryProperties["stellaops:policy.input.reachabilityWeight"]); + var ratingScore = primaryVuln.GetProperty("ratings").EnumerateArray().Single().GetProperty("score").GetDouble(); + Assert.Equal(40.5, ratingScore); + + using var usageDoc = JsonDocument.Parse(result.Usage.JsonBytes); + var usageRoot = usageDoc.RootElement; + Assert.True(usageRoot.TryGetProperty("vulnerabilities", out var usageVulnerabilities)); + var usageVulns = usageVulnerabilities.EnumerateArray().ToArray(); + Assert.Single(usageVulns); + Assert.Equal("finding-a", usageVulns[0].GetProperty("bom-ref").GetString()); } [Fact] @@ -109,16 +138,54 @@ public sealed class CycloneDxComposerTests Architecture = "amd64", }; - return SbomCompositionRequest.Create( - image, - fragments, - new DateTimeOffset(2025, 10, 19, 12, 0, 0, TimeSpan.Zero), - generatorName: "StellaOps.Scanner", - generatorVersion: "0.10.0", - properties: new Dictionary - { - ["stellaops:scanId"] = "scan-1234", - }); + return SbomCompositionRequest.Create( + image, + fragments, + new DateTimeOffset(2025, 10, 19, 12, 0, 0, TimeSpan.Zero), + generatorName: "StellaOps.Scanner", + generatorVersion: "0.10.0", + properties: new Dictionary + { + ["stellaops:scanId"] = "scan-1234", + }, + policyFindings: new[] + { + new SbomPolicyFinding + { + FindingId = "finding-a", + ComponentKey = "pkg:npm/a", + VulnerabilityId = "CVE-2025-0001", + Status = "Blocked", + Score = 40.5, + ConfigVersion = "1.0", + Quiet = true, + QuietedBy = "policy/quiet-critical-runtime", + UnknownConfidence = 0.42, + ConfidenceBand = "medium", + UnknownAgeDays = 5, + SourceTrust = "NVD", + Reachability = "runtime", + Inputs = ImmutableArray.Create( + new KeyValuePair("severityWeight", 90), + new KeyValuePair("trustWeight", 1.0), + new KeyValuePair("reachabilityWeight", 0.45)) + }, + new SbomPolicyFinding + { + FindingId = "finding-b", + ComponentKey = "pkg:npm/b", + VulnerabilityId = "CVE-2025-0002", + Status = "Warned", + Score = 12.5, + ConfigVersion = "1.0", + Quiet = false, + SourceTrust = "StellaOps", + Reachability = "indirect", + Inputs = ImmutableArray.Create( + new KeyValuePair("severityWeight", 55), + new KeyValuePair("trustWeight", 0.85)) + } + }); } private static void ValidateJson(byte[] data, int expectedComponentCount, string expectedView) @@ -128,19 +195,20 @@ public sealed class CycloneDxComposerTests Assert.True(root.TryGetProperty("metadata", out var metadata), "metadata property missing"); var properties = metadata.GetProperty("properties"); - var viewProperty = properties.EnumerateArray() - .Single(prop => prop.GetProperty("name").GetString() == "stellaops:sbom.view"); + var viewProperty = properties.EnumerateArray() + .Single(prop => string.Equals(prop.GetProperty("name").GetString(), "stellaops:sbom.view", StringComparison.Ordinal)); Assert.Equal(expectedView, viewProperty.GetProperty("value").GetString()); var components = root.GetProperty("components").EnumerateArray().ToArray(); Assert.Equal(expectedComponentCount, components.Length); - var names = components.Select(component => component.GetProperty("name").GetString()).ToArray(); - Assert.Equal(names, names.OrderBy(n => n, StringComparer.Ordinal).ToArray()); - - var firstComponentProperties = components[0].GetProperty("properties").EnumerateArray().ToDictionary( - element => element.GetProperty("name").GetString(), - element => element.GetProperty("value").GetString()); + var names = components.Select(component => component.GetProperty("name").GetString()!).ToArray(); + Assert.Equal(names, names.OrderBy(n => n, StringComparer.Ordinal).ToArray()); + + var firstComponentProperties = components[0].GetProperty("properties").EnumerateArray().ToDictionary( + element => element.GetProperty("name").GetString()!, + element => element.GetProperty("value").GetString()!, + StringComparer.Ordinal); Assert.Equal("apk", firstComponentProperties["stellaops.os.analyzer"]); Assert.Equal("x86_64", firstComponentProperties["stellaops.os.architecture"]); diff --git a/src/StellaOps.Scanner.Emit.Tests/Packaging/ScannerArtifactPackageBuilderTests.cs b/src/StellaOps.Scanner.Emit.Tests/Packaging/ScannerArtifactPackageBuilderTests.cs index 3e7b089d..e961634d 100644 --- a/src/StellaOps.Scanner.Emit.Tests/Packaging/ScannerArtifactPackageBuilderTests.cs +++ b/src/StellaOps.Scanner.Emit.Tests/Packaging/ScannerArtifactPackageBuilderTests.cs @@ -76,7 +76,7 @@ public sealed class ScannerArtifactPackageBuilderTests Assert.Equal(5, root.GetProperty("artifacts").GetArrayLength()); var usageEntry = root.GetProperty("artifacts").EnumerateArray().First(element => element.GetProperty("kind").GetString() == "sbom-usage"); - Assert.Equal("application/vnd.cyclonedx+json; version=1.5; view=usage", usageEntry.GetProperty("mediaType").GetString()); + Assert.Equal("application/vnd.cyclonedx+json; version=1.6; view=usage", usageEntry.GetProperty("mediaType").GetString()); } private static ComponentRecord CreateComponent(string key, string version, string layerDigest, ComponentUsage? usage = null, IReadOnlyDictionary? metadata = null) diff --git a/src/StellaOps.Scanner.Emit/Composition/CycloneDxComposer.cs b/src/StellaOps.Scanner.Emit/Composition/CycloneDxComposer.cs index 498127c1..577d29b1 100644 --- a/src/StellaOps.Scanner.Emit/Composition/CycloneDxComposer.cs +++ b/src/StellaOps.Scanner.Emit/Composition/CycloneDxComposer.cs @@ -6,7 +6,8 @@ using System.Linq; using System.Security.Cryptography; using System.Text; using CycloneDX; -using CycloneDX.Models; +using CycloneDX.Models; +using CycloneDX.Models.Vulnerabilities; using JsonSerializer = CycloneDX.Json.Serializer; using ProtoSerializer = CycloneDX.Protobuf.Serializer; using StellaOps.Scanner.Core.Contracts; @@ -18,10 +19,10 @@ public sealed class CycloneDxComposer { private static readonly Guid SerialNamespace = new("0d3a422b-6e1b-4d9b-9c35-654b706c97e8"); - private const string InventoryMediaTypeJson = "application/vnd.cyclonedx+json; version=1.5"; - private const string UsageMediaTypeJson = "application/vnd.cyclonedx+json; version=1.5; view=usage"; - private const string InventoryMediaTypeProtobuf = "application/vnd.cyclonedx+protobuf; version=1.5"; - private const string UsageMediaTypeProtobuf = "application/vnd.cyclonedx+protobuf; version=1.5; view=usage"; + private const string InventoryMediaTypeJson = "application/vnd.cyclonedx+json; version=1.6"; + private const string UsageMediaTypeJson = "application/vnd.cyclonedx+json; version=1.6; view=usage"; + private const string InventoryMediaTypeProtobuf = "application/vnd.cyclonedx+protobuf; version=1.6"; + private const string UsageMediaTypeProtobuf = "application/vnd.cyclonedx+protobuf; version=1.6; view=usage"; public SbomCompositionResult Compose(SbomCompositionRequest request) { @@ -77,7 +78,7 @@ public sealed class CycloneDxComposer string jsonMediaType, string protobufMediaType) { - var bom = BuildBom(request, view, components, generatedAt); + var bom = BuildBom(request, graph, view, components, generatedAt); var json = JsonSerializer.Serialize(bom); var jsonBytes = Encoding.UTF8.GetBytes(json); var protobufBytes = ProtoSerializer.Serialize(bom); @@ -100,25 +101,32 @@ public sealed class CycloneDxComposer }; } - private Bom BuildBom( - SbomCompositionRequest request, - SbomView view, - ImmutableArray components, - DateTimeOffset generatedAt) - { - var bom = new Bom - { - SpecVersion = SpecificationVersion.v1_4, - Version = 1, - Metadata = BuildMetadata(request, view, generatedAt), - Components = BuildComponents(components), - Dependencies = BuildDependencies(components), - }; - - var serialPayload = $"{request.Image.ImageDigest}|{view}|{ScannerTimestamps.ToIso8601(generatedAt)}"; - bom.SerialNumber = $"urn:uuid:{ScannerIdentifiers.CreateDeterministicGuid(SerialNamespace, Encoding.UTF8.GetBytes(serialPayload)).ToString("d", CultureInfo.InvariantCulture)}"; - - return bom; + private Bom BuildBom( + SbomCompositionRequest request, + ComponentGraph graph, + SbomView view, + ImmutableArray components, + DateTimeOffset generatedAt) + { + var bom = new Bom + { + SpecVersion = SpecificationVersion.v1_6, + Version = 1, + Metadata = BuildMetadata(request, view, generatedAt), + Components = BuildComponents(components), + Dependencies = BuildDependencies(components), + }; + + var vulnerabilities = BuildVulnerabilities(request, graph, components); + if (vulnerabilities is not null) + { + bom.Vulnerabilities = vulnerabilities; + } + + var serialPayload = $"{request.Image.ImageDigest}|{view}|{ScannerTimestamps.ToIso8601(generatedAt)}"; + bom.SerialNumber = $"urn:uuid:{ScannerIdentifiers.CreateDeterministicGuid(SerialNamespace, Encoding.UTF8.GetBytes(serialPayload)).ToString("d", CultureInfo.InvariantCulture)}"; + + return bom; } private static Metadata BuildMetadata(SbomCompositionRequest request, SbomView view, DateTimeOffset generatedAt) @@ -129,23 +137,11 @@ public sealed class CycloneDxComposer Component = BuildMetadataComponent(request.Image), }; - if (!string.IsNullOrWhiteSpace(request.GeneratorName)) - { - metadata.Tools = new List - { - new() - { - Name = request.GeneratorName, - Version = request.GeneratorVersion, - } - }; - } - - if (request.AdditionalProperties is not null && request.AdditionalProperties.Count > 0) - { - metadata.Properties = request.AdditionalProperties - .Where(static pair => !string.IsNullOrWhiteSpace(pair.Key) && pair.Value is not null) - .OrderBy(static pair => pair.Key, StringComparer.Ordinal) + if (request.AdditionalProperties is not null && request.AdditionalProperties.Count > 0) + { + metadata.Properties = request.AdditionalProperties + .Where(static pair => !string.IsNullOrWhiteSpace(pair.Key) && pair.Value is not null) + .OrderBy(static pair => pair.Key, StringComparer.Ordinal) .Select(pair => new Property { Name = pair.Key, @@ -154,15 +150,33 @@ public sealed class CycloneDxComposer .ToList(); } - if (metadata.Properties is null) - { - metadata.Properties = new List(); - } - - metadata.Properties.Add(new Property - { - Name = "stellaops:sbom.view", - Value = view.ToString().ToLowerInvariant(), + if (metadata.Properties is null) + { + metadata.Properties = new List(); + } + + if (!string.IsNullOrWhiteSpace(request.GeneratorName)) + { + metadata.Properties.Add(new Property + { + Name = "stellaops:generator.name", + Value = request.GeneratorName, + }); + + if (!string.IsNullOrWhiteSpace(request.GeneratorVersion)) + { + metadata.Properties.Add(new Property + { + Name = "stellaops:generator.version", + Value = request.GeneratorVersion, + }); + } + } + + metadata.Properties.Add(new Property + { + Name = "stellaops:sbom.view", + Value = view.ToString().ToLowerInvariant(), }); return metadata; @@ -357,8 +371,171 @@ public sealed class CycloneDxComposer }); } - return dependencies.Count == 0 ? null : dependencies; - } + return dependencies.Count == 0 ? null : dependencies; + } + + private static List? BuildVulnerabilities( + SbomCompositionRequest request, + ComponentGraph graph, + ImmutableArray viewComponents) + { + if (request.PolicyFindings.IsDefaultOrEmpty || request.PolicyFindings.Length == 0) + { + return null; + } + + if (viewComponents.IsDefaultOrEmpty || viewComponents.Length == 0) + { + return null; + } + + var componentKeys = viewComponents + .Select(static component => component.Identity.Key) + .ToImmutableHashSet(StringComparer.Ordinal); + + if (componentKeys.Count == 0) + { + return null; + } + + var vulnerabilities = new List(request.PolicyFindings.Length); + foreach (var finding in request.PolicyFindings) + { + if (!graph.ComponentMap.TryGetValue(finding.ComponentKey, out var component)) + { + continue; + } + + if (!componentKeys.Contains(component.Identity.Key)) + { + continue; + } + + var ratings = BuildRatings(finding.Score); + var properties = BuildVulnerabilityProperties(finding); + + var vulnerability = new Vulnerability + { + BomRef = finding.FindingId, + Id = finding.VulnerabilityId ?? finding.FindingId, + Source = new Source { Name = "StellaOps.Policy" }, + Affects = new List + { + new() { Ref = component.Identity.Key } + }, + Ratings = ratings, + Properties = properties, + }; + + vulnerabilities.Add(vulnerability); + } + + return vulnerabilities.Count == 0 ? null : vulnerabilities; + } + + private static List? BuildRatings(double score) + { + if (double.IsNaN(score) || double.IsInfinity(score)) + { + return null; + } + + return new List + { + new() + { + Method = ScoreMethod.Other, + Justification = "StellaOps Policy score", + Score = score, + Severity = Severity.Unknown, + Source = new Source { Name = "StellaOps.Policy" }, + } + }; + } + + private static List? BuildVulnerabilityProperties(SbomPolicyFinding finding) + { + var properties = new List(); + + AddStringProperty(properties, "stellaops:policy.status", finding.Status); + AddStringProperty(properties, "stellaops:policy.configVersion", finding.ConfigVersion); + AddBooleanProperty(properties, "stellaops:policy.quiet", finding.Quiet); + AddStringProperty(properties, "stellaops:policy.quietedBy", finding.QuietedBy); + AddStringProperty(properties, "stellaops:policy.confidenceBand", finding.ConfidenceBand); + AddStringProperty(properties, "stellaops:policy.sourceTrust", finding.SourceTrust); + AddStringProperty(properties, "stellaops:policy.reachability", finding.Reachability); + AddDoubleProperty(properties, "stellaops:policy.score", finding.Score); + AddNullableDoubleProperty(properties, "stellaops:policy.unknownConfidence", finding.UnknownConfidence); + AddNullableDoubleProperty(properties, "stellaops:policy.unknownAgeDays", finding.UnknownAgeDays); + + if (!finding.Inputs.IsDefaultOrEmpty && finding.Inputs.Length > 0) + { + foreach (var (key, value) in finding.Inputs) + { + AddDoubleProperty(properties, $"stellaops:policy.input.{key}", value); + } + } + + if (properties.Count == 0) + { + return null; + } + + properties.Sort(static (left, right) => StringComparer.Ordinal.Compare(left.Name, right.Name)); + return properties; + } + + private static void AddStringProperty(ICollection properties, string name, string? value) + { + if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(value)) + { + return; + } + + properties.Add(new Property + { + Name = name, + Value = value.Trim(), + }); + } + + private static void AddBooleanProperty(ICollection properties, string name, bool value) + { + if (string.IsNullOrWhiteSpace(name)) + { + return; + } + + properties.Add(new Property + { + Name = name, + Value = value ? "true" : "false", + }); + } + + private static void AddDoubleProperty(ICollection properties, string name, double value) + { + if (string.IsNullOrWhiteSpace(name) || double.IsNaN(value) || double.IsInfinity(value)) + { + return; + } + + properties.Add(new Property + { + Name = name, + Value = FormatDouble(value), + }); + } + + private static void AddNullableDoubleProperty(ICollection properties, string name, double? value) + { + if (!value.HasValue) + { + return; + } + + AddDoubleProperty(properties, name, value.Value); + } private static Component.Classification MapClassification(string? type) { @@ -372,7 +549,7 @@ public sealed class CycloneDxComposer "application" => Component.Classification.Application, "framework" => Component.Classification.Framework, "container" => Component.Classification.Container, - "operating-system" or "os" => Component.Classification.OperationSystem, + "operating-system" or "os" => Component.Classification.Operating_System, "device" => Component.Classification.Device, "firmware" => Component.Classification.Firmware, "file" => Component.Classification.File, @@ -396,7 +573,10 @@ public sealed class CycloneDxComposer }; } - private static string ComputeSha256(byte[] bytes) + private static string FormatDouble(double value) + => value.ToString("0.############################", CultureInfo.InvariantCulture); + + private static string ComputeSha256(byte[] bytes) { using var sha256 = SHA256.Create(); var hash = sha256.ComputeHash(bytes); diff --git a/src/StellaOps.Scanner.Emit/Composition/SbomCompositionRequest.cs b/src/StellaOps.Scanner.Emit/Composition/SbomCompositionRequest.cs index c5fb0bd8..7125dd60 100644 --- a/src/StellaOps.Scanner.Emit/Composition/SbomCompositionRequest.cs +++ b/src/StellaOps.Scanner.Emit/Composition/SbomCompositionRequest.cs @@ -39,21 +39,25 @@ public sealed record SbomCompositionRequest public string? GeneratorVersion { get; init; } = null; - public IReadOnlyDictionary? AdditionalProperties { get; init; } - = null; - - public static SbomCompositionRequest Create( - ImageArtifactDescriptor image, - IEnumerable fragments, - DateTimeOffset generatedAt, - string? generatorName = null, - string? generatorVersion = null, - IReadOnlyDictionary? properties = null) - { - ArgumentNullException.ThrowIfNull(image); - ArgumentNullException.ThrowIfNull(fragments); - - var normalizedImage = new ImageArtifactDescriptor + public IReadOnlyDictionary? AdditionalProperties { get; init; } + = null; + + public ImmutableArray PolicyFindings { get; init; } + = ImmutableArray.Empty; + + public static SbomCompositionRequest Create( + ImageArtifactDescriptor image, + IEnumerable fragments, + DateTimeOffset generatedAt, + string? generatorName = null, + string? generatorVersion = null, + IReadOnlyDictionary? properties = null, + IEnumerable? policyFindings = null) + { + ArgumentNullException.ThrowIfNull(image); + ArgumentNullException.ThrowIfNull(fragments); + + var normalizedImage = new ImageArtifactDescriptor { ImageDigest = ScannerIdentifiers.NormalizeDigest(image.ImageDigest) ?? throw new ArgumentException("Image digest is required.", nameof(image)), ImageReference = Normalize(image.ImageReference), @@ -65,21 +69,68 @@ public sealed record SbomCompositionRequest return new SbomCompositionRequest { Image = normalizedImage, - LayerFragments = fragments.ToImmutableArray(), - GeneratedAt = ScannerTimestamps.Normalize(generatedAt), - GeneratorName = Normalize(generatorName), - GeneratorVersion = Normalize(generatorVersion), - AdditionalProperties = properties, - }; - } - - private static string? Normalize(string? value) - { + LayerFragments = fragments.ToImmutableArray(), + GeneratedAt = ScannerTimestamps.Normalize(generatedAt), + GeneratorName = Normalize(generatorName), + GeneratorVersion = Normalize(generatorVersion), + AdditionalProperties = properties, + PolicyFindings = NormalizePolicyFindings(policyFindings), + }; + } + + private static string? Normalize(string? value) + { if (string.IsNullOrWhiteSpace(value)) { return null; } - - return value.Trim(); - } -} + + return value.Trim(); + } + + private static ImmutableArray NormalizePolicyFindings(IEnumerable? policyFindings) + { + if (policyFindings is null) + { + return ImmutableArray.Empty; + } + + var builder = ImmutableArray.CreateBuilder(); + foreach (var finding in policyFindings) + { + if (finding is null) + { + continue; + } + + SbomPolicyFinding normalized; + try + { + normalized = finding.Normalize(); + } + catch (ArgumentException) + { + continue; + } + + if (string.IsNullOrWhiteSpace(normalized.FindingId) || string.IsNullOrWhiteSpace(normalized.ComponentKey)) + { + continue; + } + + builder.Add(normalized); + } + + if (builder.Count == 0) + { + return ImmutableArray.Empty; + } + + return builder + .ToImmutable() + .OrderBy(static finding => finding.FindingId, StringComparer.Ordinal) + .ThenBy(static finding => finding.ComponentKey, StringComparer.Ordinal) + .ThenBy(static finding => finding.VulnerabilityId ?? string.Empty, StringComparer.Ordinal) + .ToImmutableArray(); + } +} diff --git a/src/StellaOps.Scanner.Emit/Composition/SbomPolicyFinding.cs b/src/StellaOps.Scanner.Emit/Composition/SbomPolicyFinding.cs new file mode 100644 index 00000000..d0d77ea8 --- /dev/null +++ b/src/StellaOps.Scanner.Emit/Composition/SbomPolicyFinding.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace StellaOps.Scanner.Emit.Composition; + +public sealed record SbomPolicyFinding +{ + public required string FindingId { get; init; } + + public required string ComponentKey { get; init; } + + public string? VulnerabilityId { get; init; } + + public string Status { get; init; } = string.Empty; + + public double Score { get; init; } + + public string ConfigVersion { get; init; } = string.Empty; + + public ImmutableArray> Inputs { get; init; } = ImmutableArray>.Empty; + + public string? QuietedBy { get; init; } + + public bool Quiet { get; init; } + + public double? UnknownConfidence { get; init; } + + public string? ConfidenceBand { get; init; } + + public double? UnknownAgeDays { get; init; } + + public string? SourceTrust { get; init; } + + public string? Reachability { get; init; } + + internal SbomPolicyFinding Normalize() + { + ArgumentException.ThrowIfNullOrWhiteSpace(FindingId); + ArgumentException.ThrowIfNullOrWhiteSpace(ComponentKey); + + var normalizedInputs = Inputs.IsDefaultOrEmpty + ? ImmutableArray>.Empty + : Inputs + .Where(static pair => !string.IsNullOrWhiteSpace(pair.Key)) + .Select(static pair => new KeyValuePair(pair.Key.Trim(), pair.Value)) + .OrderBy(static pair => pair.Key, StringComparer.Ordinal) + .ToImmutableArray(); + + return this with + { + FindingId = FindingId.Trim(), + ComponentKey = ComponentKey.Trim(), + VulnerabilityId = string.IsNullOrWhiteSpace(VulnerabilityId) ? null : VulnerabilityId.Trim(), + Status = string.IsNullOrWhiteSpace(Status) ? string.Empty : Status.Trim(), + ConfigVersion = string.IsNullOrWhiteSpace(ConfigVersion) ? string.Empty : ConfigVersion.Trim(), + QuietedBy = string.IsNullOrWhiteSpace(QuietedBy) ? null : QuietedBy.Trim(), + ConfidenceBand = string.IsNullOrWhiteSpace(ConfidenceBand) ? null : ConfidenceBand.Trim(), + SourceTrust = string.IsNullOrWhiteSpace(SourceTrust) ? null : SourceTrust.Trim(), + Reachability = string.IsNullOrWhiteSpace(Reachability) ? null : Reachability.Trim(), + Inputs = normalizedInputs + }; + } +} diff --git a/src/StellaOps.Scanner.Emit/StellaOps.Scanner.Emit.csproj b/src/StellaOps.Scanner.Emit/StellaOps.Scanner.Emit.csproj index 911c78f5..e4103827 100644 --- a/src/StellaOps.Scanner.Emit/StellaOps.Scanner.Emit.csproj +++ b/src/StellaOps.Scanner.Emit/StellaOps.Scanner.Emit.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/StellaOps.Scanner.Emit/TASKS.md b/src/StellaOps.Scanner.Emit/TASKS.md index 4da81e1b..b0e36c10 100644 --- a/src/StellaOps.Scanner.Emit/TASKS.md +++ b/src/StellaOps.Scanner.Emit/TASKS.md @@ -2,11 +2,11 @@ | ID | Status | Owner(s) | Depends on | Description | Exit Criteria | |----|--------|----------|------------|-------------|---------------| -| SCANNER-EMIT-10-601 | DOING (2025-10-19) | Emit Guild | SCANNER-CACHE-10-101 | Compose inventory SBOM (CycloneDX JSON/Protobuf) from layer fragments with deterministic ordering. | Inventory SBOM validated against schema; fixtures confirm deterministic output. | -| SCANNER-EMIT-10-602 | DOING (2025-10-19) | Emit Guild | SCANNER-EMIT-10-601 | Compose usage SBOM leveraging EntryTrace to flag actual usage; ensure separate view toggles. | Usage SBOM tests confirm correct subset; API contract documented. | -| SCANNER-EMIT-10-603 | DOING (2025-10-19) | Emit Guild | SCANNER-EMIT-10-601 | Generate BOM index sidecar (purl table + roaring bitmap + usedByEntrypoint flag). | Index format validated; query helpers proven; stored artifacts hashed deterministically. | -| SCANNER-EMIT-10-604 | DOING (2025-10-19) | Emit Guild | SCANNER-EMIT-10-602 | Package artifacts for export + attestation (naming, compression, manifests). | Export pipeline produces deterministic file paths/hashes; integration test with storage passes. | -| SCANNER-EMIT-10-605 | DOING (2025-10-19) | Emit Guild | SCANNER-EMIT-10-603 | Emit BOM-Index sidecar schema/fixtures (`bom-index@1`) and note CRITICAL PATH for Scheduler. | Schema + fixtures in docs/artifacts/bom-index; tests `BOMIndexGoldenIsStable` green. | -| SCANNER-EMIT-10-606 | DOING (2025-10-19) | Emit Guild | SCANNER-EMIT-10-605 | Integrate EntryTrace usage flags into BOM-Index; document semantics. | Usage bits present in sidecar; integration tests with EntryTrace fixtures pass. | +| SCANNER-EMIT-10-601 | DONE (2025-10-22) | Emit Guild | SCANNER-CACHE-10-101 | Compose inventory SBOM (CycloneDX JSON/Protobuf) from layer fragments with deterministic ordering. | Inventory SBOM validated against schema; fixtures confirm deterministic output. | +| SCANNER-EMIT-10-602 | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-601 | Compose usage SBOM leveraging EntryTrace to flag actual usage; ensure separate view toggles. | Usage SBOM tests confirm correct subset; API contract documented. | +| SCANNER-EMIT-10-603 | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-601 | Generate BOM index sidecar (purl table + roaring bitmap + usedByEntrypoint flag). | Index format validated; query helpers proven; stored artifacts hashed deterministically. | +| SCANNER-EMIT-10-604 | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-602 | Package artifacts for export + attestation (naming, compression, manifests). | Export pipeline produces deterministic file paths/hashes; integration test with storage passes. | +| SCANNER-EMIT-10-605 | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-603 | Emit BOM-Index sidecar schema/fixtures (`bom-index@1`) and note CRITICAL PATH for Scheduler. | Schema + fixtures in docs/artifacts/bom-index; tests `BOMIndexGoldenIsStable` green. | +| SCANNER-EMIT-10-606 | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-605 | Integrate EntryTrace usage flags into BOM-Index; document semantics. | Usage bits present in sidecar; integration tests with EntryTrace fixtures pass. | | SCANNER-EMIT-17-701 | TODO | Emit Guild, Native Analyzer Guild | SCANNER-EMIT-10-602 | Record GNU build-id for ELF components and surface it in inventory/usage SBOM plus diff payloads with deterministic ordering. | Native analyzer emits buildId for every ELF executable/library, SBOM/diff fixtures updated with canonical `buildId` field, regression tests prove stability, docs call out debug-symbol lookup flow. | -| SCANNER-EMIT-10-607 | TODO | Emit Guild | SCANNER-EMIT-10-604, POLICY-CORE-09-005 | Embed scoring inputs, confidence band, and `quietedBy` provenance into CycloneDX 1.6 and DSSE predicates; verify deterministic serialization. | SBOM/attestation fixtures include score, inputs, configVersion, quiet metadata; golden tests confirm canonical output. | +| SCANNER-EMIT-10-607 | DONE (2025-10-22) | Emit Guild | SCANNER-EMIT-10-604, POLICY-CORE-09-005 | Embed scoring inputs, confidence band, and `quietedBy` provenance into CycloneDX 1.6 and DSSE predicates; verify deterministic serialization. | SBOM/attestation fixtures include score, inputs, configVersion, quiet metadata; golden tests confirm canonical output. | diff --git a/src/StellaOps.Scanner.Worker/Diagnostics/TelemetryExtensions.cs b/src/StellaOps.Scanner.Worker/Diagnostics/TelemetryExtensions.cs index 2b085dd0..122efcaf 100644 --- a/src/StellaOps.Scanner.Worker/Diagnostics/TelemetryExtensions.cs +++ b/src/StellaOps.Scanner.Worker/Diagnostics/TelemetryExtensions.cs @@ -61,7 +61,8 @@ public static class TelemetryExtensions metrics .AddMeter( ScannerWorkerInstrumentation.MeterName, - "StellaOps.Scanner.Analyzers.Lang.Node") + "StellaOps.Scanner.Analyzers.Lang.Node", + "StellaOps.Scanner.Analyzers.Lang.Go") .AddRuntimeInstrumentation() .AddProcessInstrumentation();