feat: Enhance SBOM composition with policy findings and update CycloneDX package
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Docs CI / lint-and-preview (push) Has been cancelled
				
			- Added `PolicyFindings` property to `SbomCompositionRequest` to include policy findings in SBOM. - Implemented `NormalizePolicyFindings` method to process and validate policy findings. - Updated `SbomCompositionRequest.Create` method to accept policy findings as an argument. - Upgraded CycloneDX.Core package from version 5.1.0 to 10.0.1. - Marked several tasks as DONE in TASKS.md, reflecting completion of SBOM-related features. - Introduced telemetry metrics for Go analyzer to track heuristic fallbacks. - Added performance benchmarks for .NET and Go analyzers. - Created new test fixtures for .NET applications, including dependencies and runtime configurations. - Added licenses and nuspec files for logging and toolkit packages used in tests. - Implemented `SbomPolicyFinding` record to encapsulate policy finding details and normalization logic.
This commit is contained in:
		
							
								
								
									
										12
									
								
								EXECPLAN.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								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 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 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 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 | ### 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 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 | ### 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. | - 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 | - **Sprint 10** · Backlog | ||||||
|   - Team: TBD |   - Team: TBD | ||||||
|     - Path: `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md` |     - 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) |          • 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` |     - Path: `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md` | ||||||
|       1. [TODO] SCANNER-ANALYZERS-LANG-10-308G — Determinism fixtures + benchmark harness (Vs competitor). |       1. [TODO] SCANNER-ANALYZERS-LANG-10-308G — Determinism fixtures + benchmark harness (Vs competitor). | ||||||
|          • Prereqs: SCANNER-ANALYZERS-LANG-10-307G (Wave 4) |          • 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 | - **Sprint 10** · Backlog | ||||||
|   - Team: TBD |   - Team: TBD | ||||||
|     - Path: `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md` |     - 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) |          • 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` |     - 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. |       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) |          • Prereqs: SCANNER-ANALYZERS-LANG-10-308G (Wave 5) | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								SPRINTS.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								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-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-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/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-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-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. | | | 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-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-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.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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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-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 | 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 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. | | | 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 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/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 | 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. | | ||||||
|   | |||||||
| @@ -4,8 +4,10 @@ using System.Linq; | |||||||
| using System.Text.Json; | using System.Text.Json; | ||||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||||
| using StellaOps.Scanner.Analyzers.Lang; | using StellaOps.Scanner.Analyzers.Lang; | ||||||
|  | using StellaOps.Scanner.Analyzers.Lang.Go; | ||||||
| using StellaOps.Scanner.Analyzers.Lang.Java; | using StellaOps.Scanner.Analyzers.Lang.Java; | ||||||
| using StellaOps.Scanner.Analyzers.Lang.Node; | using StellaOps.Scanner.Analyzers.Lang.Node; | ||||||
|  | using StellaOps.Scanner.Analyzers.Lang.DotNet; | ||||||
|  |  | ||||||
| namespace StellaOps.Bench.ScannerAnalyzers.Scenarios; | namespace StellaOps.Bench.ScannerAnalyzers.Scenarios; | ||||||
|  |  | ||||||
| @@ -104,7 +106,9 @@ internal sealed class LanguageAnalyzerScenarioRunner : IScenarioRunner | |||||||
|         return id switch |         return id switch | ||||||
|         { |         { | ||||||
|             "java" => static () => new JavaLanguageAnalyzer(), |             "java" => static () => new JavaLanguageAnalyzer(), | ||||||
|  |             "go" => static () => new GoLanguageAnalyzer(), | ||||||
|             "node" => static () => new NodeLanguageAnalyzer(), |             "node" => static () => new NodeLanguageAnalyzer(), | ||||||
|  |             "dotnet" => static () => new DotNetLanguageAnalyzer(), | ||||||
|             _ => throw new InvalidOperationException($"Unsupported analyzer '{analyzerId}'."), |             _ => throw new InvalidOperationException($"Unsupported analyzer '{analyzerId}'."), | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -10,7 +10,9 @@ | |||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <ProjectReference Include="..\..\..\src\StellaOps.Scanner.Analyzers.Lang\StellaOps.Scanner.Analyzers.Lang.csproj" /> |     <ProjectReference Include="..\..\..\src\StellaOps.Scanner.Analyzers.Lang\StellaOps.Scanner.Analyzers.Lang.csproj" /> | ||||||
|  |     <ProjectReference Include="..\..\..\src\StellaOps.Scanner.Analyzers.Lang.Go\StellaOps.Scanner.Analyzers.Lang.Go.csproj" /> | ||||||
|     <ProjectReference Include="..\..\..\src\StellaOps.Scanner.Analyzers.Lang.Node\StellaOps.Scanner.Analyzers.Lang.Node.csproj" /> |     <ProjectReference Include="..\..\..\src\StellaOps.Scanner.Analyzers.Lang.Node\StellaOps.Scanner.Analyzers.Lang.Node.csproj" /> | ||||||
|     <ProjectReference Include="..\..\..\src\StellaOps.Scanner.Analyzers.Lang.Java\StellaOps.Scanner.Analyzers.Lang.Java.csproj" /> |     <ProjectReference Include="..\..\..\src\StellaOps.Scanner.Analyzers.Lang.Java\StellaOps.Scanner.Analyzers.Lang.Java.csproj" /> | ||||||
|  |     <ProjectReference Include="..\..\..\src\StellaOps.Scanner.Analyzers.Lang.DotNet\StellaOps.Scanner.Analyzers.Lang.DotNet.csproj" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| scenario,iterations,sample_count,mean_ms,p95_ms,max_ms | scenario,iterations,sample_count,mean_ms,p95_ms,max_ms | ||||||
| node_monorepo_walk,5,4,4.2314,15.3277,18.9984 | node_monorepo_walk,5,4,9.4303,36.1354,45.0012 | ||||||
| java_demo_archive,5,1,4.5572,17.3489,21.5472 | java_demo_archive,5,1,20.6964,81.5592,101.7846 | ||||||
| python_site_packages_walk,5,3,2.0049,6.4230,7.8832 | 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 | ||||||
|   | |||||||
| 
 | 
| @@ -18,6 +18,22 @@ | |||||||
|         "java" |         "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", |       "id": "python_site_packages_walk", | ||||||
|       "label": "Python site-packages dist-info crawl", |       "label": "Python site-packages dist-info crawl", | ||||||
|   | |||||||
| @@ -10,3 +10,16 @@ Pending tasks: | |||||||
| - LA5: Rust heuristic coverage comparisons. | - LA5: Rust heuristic coverage comparisons. | ||||||
|  |  | ||||||
| Results should be committed as deterministic CSV/JSON outputs with accompanying methodology notes. | 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. | ||||||
|   | |||||||
| @@ -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 | ||||||
| 
 | 
| @@ -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 | ||||||
| 
 | 
| @@ -17,11 +17,11 @@ completely isolated network: | |||||||
| | **Provenance** | Cosign signature, SPDX 2.3 SBOM, in‑toto SLSA attestation | | | **Provenance** | Cosign signature, SPDX 2.3 SBOM, in‑toto SLSA attestation | | ||||||
| | **Attested manifest** | `offline-manifest.json` + detached JWS covering bundle metadata, signed during export. | | | **Attested manifest** | `offline-manifest.json` + detached JWS covering bundle metadata, signed during export. | | ||||||
| | **Delta patches** | Daily diff bundles keep size \< 350 MB | | | **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. | **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 }}**.   | *Scanner core:* C# 12 on **.NET {{ dotnet }}**.   | ||||||
| *Imports are idempotent and atomic — no service downtime.* | *Imports are idempotent and atomic — no service downtime.* | ||||||
| @@ -60,6 +60,47 @@ jq '.artifacts[] | {name, sha256, size, capturedAt}' offline-manifest-<DATE>.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. | 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. | ||||||
|  |  | ||||||
|  | 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 | ## 2 · Import on the air‑gapped host | ||||||
| @@ -86,6 +127,14 @@ The CLI validates recorded digests (when `.metadata.json` is present) before str | |||||||
| * Old feeds are kept until the new bundle is fully verified. | * Old feeds are kept until the new bundle is fully verified. | ||||||
| * Import time on a SATA SSD: ≈ 25 s for a 300 MB kit. | * 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-<DATE>.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 | ## 3 · Delta patch workflow | ||||||
|   | |||||||
| @@ -356,6 +356,7 @@ scanner: | |||||||
|   * `scanner.layer_cache_hits_total`, `scanner.file_cas_hits_total` |   * `scanner.layer_cache_hits_total`, `scanner.file_cas_hits_total` | ||||||
|   * `scanner.artifact_bytes_total{format}` |   * `scanner.artifact_bytes_total{format}` | ||||||
|   * `scanner.attestation_latency_seconds`, `scanner.rekor_failures_total` |   * `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. | * **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. | * **Audit logs**: DSSE requests log `license_id`, `image_digest`, `artifactSha256`, `policy_digest?`, Rekor UUID on success. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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. | - 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. | - 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. | - 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: | Next steps: | ||||||
| - Concelier WebService owners to link this update in the next deployment bulletin once FEEDWEB-DOCS-01-001 clears review. | - Concelier WebService owners to link this update in the next deployment bulletin once FEEDWEB-DOCS-01-001 clears review. | ||||||
|   | |||||||
| @@ -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-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-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. | | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| using System; | using System; | ||||||
| using CycloneDX.Models; | using CycloneDX.Models; | ||||||
|  |  | ||||||
| var dependenciesProperty = typeof(Dependency).GetProperty("Dependencies")!; | Console.WriteLine(string.Join(", ", Enum.GetNames(typeof(Component.Classification)))); | ||||||
| Console.WriteLine(dependenciesProperty.PropertyType); |  | ||||||
| Console.WriteLine(dependenciesProperty.PropertyType.GenericTypeArguments[0]); |  | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="CycloneDX.Core" Version="5.1.0" /> |     <PackageReference Include="CycloneDX.Core" Version="10.0.1" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ | |||||||
|       "blocked": 1, |       "blocked": 1, | ||||||
|       "warned": 0, |       "warned": 0, | ||||||
|       "ignored": 0, |       "ignored": 0, | ||||||
|       "quieted": 0 |       "quieted": 1 | ||||||
|     }, |     }, | ||||||
|     "verdicts": [ |     "verdicts": [ | ||||||
|       { |       { | ||||||
| @@ -29,9 +29,15 @@ | |||||||
|           "severityWeight": 90, |           "severityWeight": 90, | ||||||
|           "trustWeight": 1, |           "trustWeight": 1, | ||||||
|           "trustWeight.NVD": 1, |           "trustWeight.NVD": 1, | ||||||
|           "reachability.runtime": 0.45 |           "reachability.runtime": 0.45, | ||||||
|  |           "unknownConfidence": 0.52, | ||||||
|  |           "unknownAgeDays": 4 | ||||||
|         }, |         }, | ||||||
|         "quiet": false, |         "quietedBy": "policy/quiet-critical-runtime", | ||||||
|  |         "quiet": true, | ||||||
|  |         "unknownConfidence": 0.52, | ||||||
|  |         "confidenceBand": "medium", | ||||||
|  |         "unknownAgeDays": 4, | ||||||
|         "sourceTrust": "NVD", |         "sourceTrust": "NVD", | ||||||
|         "reachability": "runtime" |         "reachability": "runtime" | ||||||
|       } |       } | ||||||
| @@ -40,7 +46,7 @@ | |||||||
|   }, |   }, | ||||||
|   "dsse": { |   "dsse": { | ||||||
|     "payloadType": "application/vnd.stellaops.report+json", |     "payloadType": "application/vnd.stellaops.report+json", | ||||||
|     "payload": "eyJyZXBvcnRJZCI6InJlcG9ydC0zZGVmNWYzNjJhYTQ3NWVmMTRiNiIsImltYWdlRGlnZXN0Ijoic2hhMjU2OmRlYWRiZWVmIiwiZ2VuZXJhdGVkQXQiOiIyMDI1LTEwLTE5VDA4OjI4OjA5LjM2OTkyNjcrMDA6MDAiLCJ2ZXJkaWN0IjoiYmxvY2tlZCIsInBvbGljeSI6eyJyZXZpc2lvbklkIjoicmV2LTEiLCJkaWdlc3QiOiIyN2QyZWMyYjM0ZmVlZGMzMDRmYzU2NGQyNTJlY2VlMWM4ZmExNGVhNTgxYTVmZjVjMWVhODk2MzMxM2Q1YzhkIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwicnVsZU5hbWUiOiJCbG9jayBDcml0aWNhbCIsInJ1bGVBY3Rpb24iOiJCbG9jayIsInNjb3JlIjo0MC41LCJjb25maWdWZXJzaW9uIjoiMS4wIiwiaW5wdXRzIjp7InJlYWNoYWJpbGl0eVdlaWdodCI6MC40NSwiYmFzZVNjb3JlIjo0MC41LCJzZXZlcml0eVdlaWdodCI6OTAsInRydXN0V2VpZ2h0IjoxLCJ0cnVzdFdlaWdodC5OVkQiOjEsInJlYWNoYWJpbGl0eS5ydW50aW1lIjowLjQ1fSwicXVpZXQiOmZhbHNlLCJzb3VyY2VUcnVzdCI6Ik5WRCIsInJlYWNoYWJpbGl0eSI6InJ1bnRpbWUifV0sImlzc3VlcyI6W119", |     "payload": "eyJyZXBvcnRJZCI6InJlcG9ydC0zZGVmNWYzNjJhYTQ3NWVmMTRiNiIsImltYWdlRGlnZXN0Ijoic2hhMjU2OmRlYWRiZWVmIiwiZ2VuZXJhdGVkQXQiOiIyMDI1LTEwLTE5VDA4OjI4OjA5LjM2OTkyNjcrMDA6MDAiLCJ2ZXJkaWN0IjoiYmxvY2tlZCIsInBvbGljeSI6eyJyZXZpc2lvbklkIjoicmV2LTEiLCJkaWdlc3QiOiIyN2QyZWMyYjM0ZmVlZGMzMDRmYzU2NGQyNTJlY2VlMWM4ZmExNGVhNTgxYTVmZjVjMWVhODk2MzMxM2Q1YzhkIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MX0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwicnVsZU5hbWUiOiJCbG9jayBDcml0aWNhbCIsInJ1bGVBY3Rpb24iOiJCbG9jayIsInNjb3JlIjo0MC41LCJjb25maWdWZXJzaW9uIjoiMS4wIiwiaW5wdXRzIjp7InJlYWNoYWJpbGl0eVdlaWdodCI6MC40NSwiYmFzZVNjb3JlIjo0MC41LCJzZXZlcml0eVdlaWdodCI6OTAsInRydXN0V2VpZ2h0IjoxLCJ0cnVzdFdlaWdodC5OVkQiOjEsInJlYWNoYWJpbGl0eS5ydW50aW1lIjowLjQ1LCJ1bmtub3duQ29uZmlkZW5jZSI6MC41MiwidW5rbm93bkFnZURheXMiOjR9LCJxdWlldGVkQnkiOiJwb2xpY3kvcXVpZXQtY3JpdGljYWwtcnVudGltZSIsInF1aWV0Ijp0cnVlLCJ1bmtub3duQ29uZmlkZW5jZSI6MC41MiwiY29uZmlkZW5jZUJhbmQiOiJtZWRpdW0iLCJ1bmtub3duQWdlRGF5cyI6NCwic291cmNlVHJ1c3QiOiJOVkQiLCJyZWFjaGFiaWxpdHkiOiJydW50aW1lIn1dLCJpc3N1ZXMiOltdfQ==", | ||||||
|     "signatures": [ |     "signatures": [ | ||||||
|       { |       { | ||||||
|         "keyId": "scanner-report-signing", |         "keyId": "scanner-report-signing", | ||||||
|   | |||||||
| @@ -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. | | | 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. | | | 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. | | | 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. | | | 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 | 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. | | | 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. | | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| [ | [ | ||||||
|   { |   { | ||||||
|     "analyzerId": "golang", |     "analyzerId": "golang", | ||||||
|     "componentKey": "golang::bin::sha256:80f528c90b72a4c4cc3fa078501154e4f2a3f49faea3ec380112d61740bde4c3", |     "componentKey": "golang::bin::sha256:7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99", | ||||||
|     "name": "app", |     "name": "app", | ||||||
|     "type": "bin", |     "type": "bin", | ||||||
|     "usedByEntrypoint": false, |     "usedByEntrypoint": false, | ||||||
|     "metadata": { |     "metadata": { | ||||||
|       "binary.sha256": "80f528c90b72a4c4cc3fa078501154e4f2a3f49faea3ec380112d61740bde4c3", |       "binary.sha256": "7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99", | ||||||
|       "binaryPath": "app", |       "binaryPath": "app", | ||||||
|       "go.version.hint": "go1.22.8", |       "go.version.hint": "go1.22.8", | ||||||
|       "languageHint": "golang", |       "languageHint": "golang", | ||||||
| @@ -17,7 +17,7 @@ | |||||||
|         "kind": "file", |         "kind": "file", | ||||||
|         "source": "binary", |         "source": "binary", | ||||||
|         "locator": "app", |         "locator": "app", | ||||||
|         "sha256": "80f528c90b72a4c4cc3fa078501154e4f2a3f49faea3ec380112d61740bde4c3" |         "sha256": "7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99" | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         "kind": "metadata", |         "kind": "metadata", | ||||||
|   | |||||||
| @@ -1,4 +1,7 @@ | |||||||
|  | using System; | ||||||
|  | using System.Diagnostics.Metrics; | ||||||
| using System.IO; | using System.IO; | ||||||
|  | using System.Linq; | ||||||
| using StellaOps.Scanner.Analyzers.Lang.Go; | using StellaOps.Scanner.Analyzers.Lang.Go; | ||||||
| using StellaOps.Scanner.Analyzers.Lang.Tests.Harness; | using StellaOps.Scanner.Analyzers.Lang.Tests.Harness; | ||||||
| using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities; | using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities; | ||||||
| @@ -63,4 +66,69 @@ public sealed class GoLanguageAnalyzerTests | |||||||
|             analyzers, |             analyzers, | ||||||
|             cancellationToken); |             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<long>((_, 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)); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,14 +16,16 @@ Build the Go analyzer plug-in that reads Go build info, module metadata, and DWA | |||||||
|  |  | ||||||
| ## Expectations | ## Expectations | ||||||
| - Latency targets: ≤400 µs (hot) / ≤2 ms (cold) per binary; minimal allocations via buffer pooling. | - Latency targets: ≤400 µs (hot) / ≤2 ms (cold) per binary; minimal allocations via buffer pooling. | ||||||
|  | - Shared buffer pooling via `ArrayPool<byte>` for build-info/DWARF reads; safe for concurrent scans. | ||||||
| - Deterministic fallback to `bin:{sha256}` when metadata absent; heuristics clearly identified. | - Deterministic fallback to `bin:{sha256}` when metadata absent; heuristics clearly identified. | ||||||
| - Offline-first: rely solely on embedded metadata. | - Offline-first: rely solely on embedded metadata. | ||||||
| - Telemetry for binaries processed, metadata coverage, heuristics usage. | - 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 | ## Dependencies | ||||||
| - Shared language analyzer core; Worker dispatcher; caching infrastructure (layer cache + file CAS). | - Shared language analyzer core; Worker dispatcher; caching infrastructure (layer cache + file CAS). | ||||||
|  |  | ||||||
| ## Testing & Artifacts | ## Testing & Artifacts | ||||||
| - Golden fixtures for modules with/without VCS info, stripped binaries, cross-compiled variants. | - Golden fixtures for modules with/without VCS info, stripped binaries, cross-compiled variants. | ||||||
| - Benchmark comparison with competitor scanners to demonstrate speed/fidelity advantages. | - Benchmark comparison with competitor scanners to demonstrate speed/fidelity advantages (captured in `bench/Scanner.Analyzers/lang/go/`). | ||||||
| - ADR documenting heuristics and risk mitigation. | - ADR documenting heuristics and risk mitigation. | ||||||
|   | |||||||
| @@ -233,6 +233,8 @@ public sealed class GoLanguageAnalyzer : ILanguageAnalyzer | |||||||
|             metadata: metadata, |             metadata: metadata, | ||||||
|             evidence: evidence, |             evidence: evidence, | ||||||
|             usedByEntrypoint: usedByEntrypoint); |             usedByEntrypoint: usedByEntrypoint); | ||||||
|  |  | ||||||
|  |         GoAnalyzerMetrics.RecordHeuristic(strippedBinary.Indicator, !string.IsNullOrEmpty(strippedBinary.GoVersionHint)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static IEnumerable<LanguageComponentEvidence> BuildEvidence(GoBuildInfo buildInfo, GoModule module, string binaryRelativePath, LanguageAnalyzerContext context, ref string? binaryHash) |     private static IEnumerable<LanguageComponentEvidence> BuildEvidence(GoBuildInfo buildInfo, GoModule module, string binaryRelativePath, LanguageAnalyzerContext context, ref string? binaryHash) | ||||||
|   | |||||||
| @@ -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<long> HeuristicCounter = Meter.CreateCounter<long>( | ||||||
|  |         "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<string, object?>("indicator", NormalizeIndicator(indicator)), | ||||||
|  |             new KeyValuePair<string, object?>("version_hint", hasVersionHint ? "present" : "absent")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static string NormalizeIndicator(GoStrippedBinaryIndicator indicator) | ||||||
|  |         => indicator switch | ||||||
|  |         { | ||||||
|  |             GoStrippedBinaryIndicator.BuildId => "build-id", | ||||||
|  |             GoStrippedBinaryIndicator.GoRuntimeMarkers => "runtime-markers", | ||||||
|  |             _ => "unknown", | ||||||
|  |         }; | ||||||
|  | } | ||||||
| @@ -38,16 +38,59 @@ internal static class GoBinaryScanner | |||||||
|         goVersion = null; |         goVersion = null; | ||||||
|         moduleData = null; |         moduleData = null; | ||||||
|  |  | ||||||
|  |         FileInfo info; | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             var info = new FileInfo(filePath); |             info = new FileInfo(filePath); | ||||||
|             if (!info.Exists || info.Length < 64 || info.Length > 128 * 1024 * 1024) |             if (!info.Exists || info.Length < 64 || info.Length > 128 * 1024 * 1024) | ||||||
|             { |             { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |         catch (IOException) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         catch (UnauthorizedAccessException) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         catch (System.Security.SecurityException) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|             var data = File.ReadAllBytes(filePath); |         var length = info.Length; | ||||||
|             var span = new ReadOnlySpan<byte>(data); |         if (length <= 0) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var inspectLength = (int)Math.Min(length, int.MaxValue); | ||||||
|  |         var buffer = ArrayPool<byte>.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<byte>(buffer, 0, totalRead); | ||||||
|             var offset = span.IndexOf(BuildInfoMagic.Span); |             var offset = span.IndexOf(BuildInfoMagic.Span); | ||||||
|             if (offset < 0) |             if (offset < 0) | ||||||
|             { |             { | ||||||
| @@ -65,6 +108,11 @@ internal static class GoBinaryScanner | |||||||
|         { |         { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  |         finally | ||||||
|  |         { | ||||||
|  |             Array.Clear(buffer, 0, inspectLength); | ||||||
|  |             ArrayPool<byte>.Shared.Return(buffer); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static bool TryClassifyStrippedBinary(string filePath, out GoStrippedBinaryClassification classification) |     public static bool TryClassifyStrippedBinary(string filePath, out GoStrippedBinaryClassification classification) | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System; | using System; | ||||||
|  | using System.Buffers; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Text; | using System.Text; | ||||||
|  |  | ||||||
| @@ -15,16 +16,10 @@ internal static class GoDwarfReader | |||||||
|     { |     { | ||||||
|         metadata = null; |         metadata = null; | ||||||
|  |  | ||||||
|         ReadOnlySpan<byte> data; |         FileInfo fileInfo; | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             var fileInfo = new FileInfo(path); |             fileInfo = new FileInfo(path); | ||||||
|             if (!fileInfo.Exists || fileInfo.Length == 0 || fileInfo.Length > 256 * 1024 * 1024) |  | ||||||
|             { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             data = File.ReadAllBytes(path); |  | ||||||
|         } |         } | ||||||
|         catch (IOException) |         catch (IOException) | ||||||
|         { |         { | ||||||
| @@ -35,6 +30,27 @@ internal static class GoDwarfReader | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (!fileInfo.Exists || fileInfo.Length == 0 || fileInfo.Length > 256 * 1024 * 1024) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var length = fileInfo.Length; | ||||||
|  |         var readLength = (int)Math.Min(length, int.MaxValue); | ||||||
|  |         var buffer = ArrayPool<byte>.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<byte>(buffer, 0, bytesRead); | ||||||
|  |  | ||||||
|             var revision = ExtractValue(data, VcsRevisionToken); |             var revision = ExtractValue(data, VcsRevisionToken); | ||||||
|             var modifiedText = ExtractValue(data, VcsModifiedToken); |             var modifiedText = ExtractValue(data, VcsModifiedToken); | ||||||
|             var timestamp = ExtractValue(data, VcsTimeToken); |             var timestamp = ExtractValue(data, VcsTimeToken); | ||||||
| @@ -57,6 +73,20 @@ internal static class GoDwarfReader | |||||||
|             metadata = new GoDwarfMetadata(system, revision, modified, timestamp); |             metadata = new GoDwarfMetadata(system, revision, modified, timestamp); | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  |         catch (IOException) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         catch (UnauthorizedAccessException) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         finally | ||||||
|  |         { | ||||||
|  |             Array.Clear(buffer, 0, bytesRead); | ||||||
|  |             ArrayPool<byte>.Shared.Return(buffer); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private static string? ExtractValue(ReadOnlySpan<byte> data, ReadOnlySpan<byte> token) |     private static string? ExtractValue(ReadOnlySpan<byte> data, ReadOnlySpan<byte> token) | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -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. | | | 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 %. | | | 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. | | | 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. | | | 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 | 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. | | | 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 | 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. | | | 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 | 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. | | | 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. | | ||||||
|   | |||||||
| @@ -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. | | | 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. | | | 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. | | | 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. | | | 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. | | | 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. | | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| using System; | using System; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Linq; | using System.Linq; | ||||||
|  | using System.Text.Json; | ||||||
| using System.Threading; | using System.Threading; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using StellaOps.Scanner.Analyzers.Lang.DotNet; | using StellaOps.Scanner.Analyzers.Lang.DotNet; | ||||||
| @@ -103,6 +104,54 @@ public sealed class DotNetLanguageAnalyzerTests | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     [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 |     private sealed class StubAuthenticodeInspector : IDotNetAuthenticodeInspector | ||||||
|     { |     { | ||||||
|         public DotNetAuthenticodeMetadata? TryInspect(string assemblyPath, CancellationToken cancellationToken) |         public DotNetAuthenticodeMetadata? TryInspect(string assemblyPath, CancellationToken cancellationToken) | ||||||
|   | |||||||
| @@ -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" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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" | ||||||
|  |           ] | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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" | ||||||
|  |           ] | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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" | ||||||
|  |       } | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | ] | ||||||
| @@ -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. | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> | ||||||
|  |   <metadata> | ||||||
|  |     <id>StellaOps.Logging</id> | ||||||
|  |     <version>2.5.1</version> | ||||||
|  |     <authors>StellaOps</authors> | ||||||
|  |     <description>Logging sample package for analyzer fixtures.</description> | ||||||
|  |     <license type="expression">Apache-2.0</license> | ||||||
|  |     <licenseUrl>https://stella-ops.example/licenses/logging</licenseUrl> | ||||||
|  |     <projectUrl>https://stella-ops.example/projects/logging</projectUrl> | ||||||
|  |   </metadata> | ||||||
|  | </package> | ||||||
| @@ -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. | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> | ||||||
|  |   <metadata> | ||||||
|  |     <id>StellaOps.Toolkit</id> | ||||||
|  |     <version>1.2.3</version> | ||||||
|  |     <authors>StellaOps</authors> | ||||||
|  |     <description>Toolkit sample package for analyzer fixtures.</description> | ||||||
|  |     <license type="file">LICENSE.txt</license> | ||||||
|  |     <licenseUrl>https://stella-ops.example/licenses/toolkit</licenseUrl> | ||||||
|  |   </metadata> | ||||||
|  | </package> | ||||||
| @@ -68,6 +68,7 @@ All sprints below assume prerequisites from SP10-G2 (core scaffolding + Java ana | |||||||
|   - Tests verifying dual runtimeconfig merge logic. |   - Tests verifying dual runtimeconfig merge logic. | ||||||
|   - Guidance for Policy on license propagation from NuGet metadata. |   - 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-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) | ## 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. | - **Scope:** Detect crates via metadata in `.fingerprint`, Cargo.lock fragments, or embedded `rustc` markers; robust fallback to binary hash classification. | ||||||
|   | |||||||
| @@ -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-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-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-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-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-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. | | | 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. | | ||||||
|   | |||||||
| @@ -21,17 +21,46 @@ public sealed class CycloneDxComposerTests | |||||||
|  |  | ||||||
|         Assert.NotNull(result.Inventory); |         Assert.NotNull(result.Inventory); | ||||||
|         Assert.StartsWith("urn:uuid:", result.Inventory.SerialNumber, StringComparison.Ordinal); |         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+json; version=1.6", result.Inventory.JsonMediaType); | ||||||
|         Assert.Equal("application/vnd.cyclonedx+protobuf; version=1.5", result.Inventory.ProtobufMediaType); |         Assert.Equal("application/vnd.cyclonedx+protobuf; version=1.6", result.Inventory.ProtobufMediaType); | ||||||
|         Assert.Equal(2, result.Inventory.Components.Length); |         Assert.Equal(2, result.Inventory.Components.Length); | ||||||
|  |  | ||||||
|         Assert.NotNull(result.Usage); |         Assert.NotNull(result.Usage); | ||||||
|         Assert.Equal("application/vnd.cyclonedx+json; version=1.5; view=usage", result.Usage!.JsonMediaType); |         Assert.Equal("application/vnd.cyclonedx+json; version=1.6; view=usage", result.Usage!.JsonMediaType); | ||||||
|         Assert.Single(result.Usage.Components); |         Assert.Single(result.Usage.Components); | ||||||
|         Assert.Equal("pkg:npm/a", result.Usage.Components[0].Identity.Key); |         Assert.Equal("pkg:npm/a", result.Usage.Components[0].Identity.Key); | ||||||
|  |  | ||||||
|         ValidateJson(result.Inventory.JsonBytes, expectedComponentCount: 2, expectedView: "inventory"); |         ValidateJson(result.Inventory.JsonBytes, expectedComponentCount: 2, expectedView: "inventory"); | ||||||
|         ValidateJson(result.Usage.JsonBytes, expectedComponentCount: 1, expectedView: "usage"); |         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] |     [Fact] | ||||||
| @@ -118,6 +147,44 @@ public sealed class CycloneDxComposerTests | |||||||
|             properties: new Dictionary<string, string> |             properties: new Dictionary<string, string> | ||||||
|             { |             { | ||||||
|                 ["stellaops:scanId"] = "scan-1234", |                 ["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<string, double>("severityWeight", 90), | ||||||
|  |                         new KeyValuePair<string, double>("trustWeight", 1.0), | ||||||
|  |                         new KeyValuePair<string, double>("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<string, double>("severityWeight", 55), | ||||||
|  |                         new KeyValuePair<string, double>("trustWeight", 0.85)) | ||||||
|  |                 } | ||||||
|             }); |             }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -129,18 +196,19 @@ public sealed class CycloneDxComposerTests | |||||||
|         Assert.True(root.TryGetProperty("metadata", out var metadata), "metadata property missing"); |         Assert.True(root.TryGetProperty("metadata", out var metadata), "metadata property missing"); | ||||||
|         var properties = metadata.GetProperty("properties"); |         var properties = metadata.GetProperty("properties"); | ||||||
|         var viewProperty = properties.EnumerateArray() |         var viewProperty = properties.EnumerateArray() | ||||||
|             .Single(prop => prop.GetProperty("name").GetString() == "stellaops:sbom.view"); |             .Single(prop => string.Equals(prop.GetProperty("name").GetString(), "stellaops:sbom.view", StringComparison.Ordinal)); | ||||||
|         Assert.Equal(expectedView, viewProperty.GetProperty("value").GetString()); |         Assert.Equal(expectedView, viewProperty.GetProperty("value").GetString()); | ||||||
|  |  | ||||||
|         var components = root.GetProperty("components").EnumerateArray().ToArray(); |         var components = root.GetProperty("components").EnumerateArray().ToArray(); | ||||||
|         Assert.Equal(expectedComponentCount, components.Length); |         Assert.Equal(expectedComponentCount, components.Length); | ||||||
|  |  | ||||||
|         var names = components.Select(component => component.GetProperty("name").GetString()).ToArray(); |         var names = components.Select(component => component.GetProperty("name").GetString()!).ToArray(); | ||||||
|         Assert.Equal(names, names.OrderBy(n => n, StringComparer.Ordinal).ToArray()); |         Assert.Equal(names, names.OrderBy(n => n, StringComparer.Ordinal).ToArray()); | ||||||
|  |  | ||||||
|         var firstComponentProperties = components[0].GetProperty("properties").EnumerateArray().ToDictionary( |         var firstComponentProperties = components[0].GetProperty("properties").EnumerateArray().ToDictionary( | ||||||
|             element => element.GetProperty("name").GetString(), |             element => element.GetProperty("name").GetString()!, | ||||||
|             element => element.GetProperty("value").GetString()); |             element => element.GetProperty("value").GetString()!, | ||||||
|  |             StringComparer.Ordinal); | ||||||
|  |  | ||||||
|         Assert.Equal("apk", firstComponentProperties["stellaops.os.analyzer"]); |         Assert.Equal("apk", firstComponentProperties["stellaops.os.analyzer"]); | ||||||
|         Assert.Equal("x86_64", firstComponentProperties["stellaops.os.architecture"]); |         Assert.Equal("x86_64", firstComponentProperties["stellaops.os.architecture"]); | ||||||
|   | |||||||
| @@ -76,7 +76,7 @@ public sealed class ScannerArtifactPackageBuilderTests | |||||||
|         Assert.Equal(5, root.GetProperty("artifacts").GetArrayLength()); |         Assert.Equal(5, root.GetProperty("artifacts").GetArrayLength()); | ||||||
|  |  | ||||||
|         var usageEntry = root.GetProperty("artifacts").EnumerateArray().First(element => element.GetProperty("kind").GetString() == "sbom-usage"); |         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<string, string>? metadata = null) |     private static ComponentRecord CreateComponent(string key, string version, string layerDigest, ComponentUsage? usage = null, IReadOnlyDictionary<string, string>? metadata = null) | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ using System.Security.Cryptography; | |||||||
| using System.Text; | using System.Text; | ||||||
| using CycloneDX; | using CycloneDX; | ||||||
| using CycloneDX.Models; | using CycloneDX.Models; | ||||||
|  | using CycloneDX.Models.Vulnerabilities; | ||||||
| using JsonSerializer = CycloneDX.Json.Serializer; | using JsonSerializer = CycloneDX.Json.Serializer; | ||||||
| using ProtoSerializer = CycloneDX.Protobuf.Serializer; | using ProtoSerializer = CycloneDX.Protobuf.Serializer; | ||||||
| using StellaOps.Scanner.Core.Contracts; | 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 static readonly Guid SerialNamespace = new("0d3a422b-6e1b-4d9b-9c35-654b706c97e8"); | ||||||
|  |  | ||||||
|     private const string InventoryMediaTypeJson = "application/vnd.cyclonedx+json; version=1.5"; |     private const string InventoryMediaTypeJson = "application/vnd.cyclonedx+json; version=1.6"; | ||||||
|     private const string UsageMediaTypeJson = "application/vnd.cyclonedx+json; version=1.5; view=usage"; |     private const string UsageMediaTypeJson = "application/vnd.cyclonedx+json; version=1.6; view=usage"; | ||||||
|     private const string InventoryMediaTypeProtobuf = "application/vnd.cyclonedx+protobuf; version=1.5"; |     private const string InventoryMediaTypeProtobuf = "application/vnd.cyclonedx+protobuf; version=1.6"; | ||||||
|     private const string UsageMediaTypeProtobuf = "application/vnd.cyclonedx+protobuf; version=1.5; view=usage"; |     private const string UsageMediaTypeProtobuf = "application/vnd.cyclonedx+protobuf; version=1.6; view=usage"; | ||||||
|  |  | ||||||
|     public SbomCompositionResult Compose(SbomCompositionRequest request) |     public SbomCompositionResult Compose(SbomCompositionRequest request) | ||||||
|     { |     { | ||||||
| @@ -77,7 +78,7 @@ public sealed class CycloneDxComposer | |||||||
|         string jsonMediaType, |         string jsonMediaType, | ||||||
|         string protobufMediaType) |         string protobufMediaType) | ||||||
|     { |     { | ||||||
|         var bom = BuildBom(request, view, components, generatedAt); |         var bom = BuildBom(request, graph, view, components, generatedAt); | ||||||
|         var json = JsonSerializer.Serialize(bom); |         var json = JsonSerializer.Serialize(bom); | ||||||
|         var jsonBytes = Encoding.UTF8.GetBytes(json); |         var jsonBytes = Encoding.UTF8.GetBytes(json); | ||||||
|         var protobufBytes = ProtoSerializer.Serialize(bom); |         var protobufBytes = ProtoSerializer.Serialize(bom); | ||||||
| @@ -102,19 +103,26 @@ public sealed class CycloneDxComposer | |||||||
|  |  | ||||||
|     private Bom BuildBom( |     private Bom BuildBom( | ||||||
|         SbomCompositionRequest request, |         SbomCompositionRequest request, | ||||||
|  |         ComponentGraph graph, | ||||||
|         SbomView view, |         SbomView view, | ||||||
|         ImmutableArray<AggregatedComponent> components, |         ImmutableArray<AggregatedComponent> components, | ||||||
|         DateTimeOffset generatedAt) |         DateTimeOffset generatedAt) | ||||||
|     { |     { | ||||||
|         var bom = new Bom |         var bom = new Bom | ||||||
|         { |         { | ||||||
|             SpecVersion = SpecificationVersion.v1_4, |             SpecVersion = SpecificationVersion.v1_6, | ||||||
|             Version = 1, |             Version = 1, | ||||||
|             Metadata = BuildMetadata(request, view, generatedAt), |             Metadata = BuildMetadata(request, view, generatedAt), | ||||||
|             Components = BuildComponents(components), |             Components = BuildComponents(components), | ||||||
|             Dependencies = BuildDependencies(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)}"; |         var serialPayload = $"{request.Image.ImageDigest}|{view}|{ScannerTimestamps.ToIso8601(generatedAt)}"; | ||||||
|         bom.SerialNumber = $"urn:uuid:{ScannerIdentifiers.CreateDeterministicGuid(SerialNamespace, Encoding.UTF8.GetBytes(serialPayload)).ToString("d", CultureInfo.InvariantCulture)}"; |         bom.SerialNumber = $"urn:uuid:{ScannerIdentifiers.CreateDeterministicGuid(SerialNamespace, Encoding.UTF8.GetBytes(serialPayload)).ToString("d", CultureInfo.InvariantCulture)}"; | ||||||
|  |  | ||||||
| @@ -129,18 +137,6 @@ public sealed class CycloneDxComposer | |||||||
|             Component = BuildMetadataComponent(request.Image), |             Component = BuildMetadataComponent(request.Image), | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         if (!string.IsNullOrWhiteSpace(request.GeneratorName)) |  | ||||||
|         { |  | ||||||
|             metadata.Tools = new List<Tool> |  | ||||||
|             { |  | ||||||
|                 new() |  | ||||||
|                 { |  | ||||||
|                     Name = request.GeneratorName, |  | ||||||
|                     Version = request.GeneratorVersion, |  | ||||||
|                 } |  | ||||||
|             }; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (request.AdditionalProperties is not null && request.AdditionalProperties.Count > 0) |         if (request.AdditionalProperties is not null && request.AdditionalProperties.Count > 0) | ||||||
|         { |         { | ||||||
|             metadata.Properties = request.AdditionalProperties |             metadata.Properties = request.AdditionalProperties | ||||||
| @@ -159,6 +155,24 @@ public sealed class CycloneDxComposer | |||||||
|             metadata.Properties = new List<Property>(); |             metadata.Properties = new List<Property>(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         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 |         metadata.Properties.Add(new Property | ||||||
|         { |         { | ||||||
|             Name = "stellaops:sbom.view", |             Name = "stellaops:sbom.view", | ||||||
| @@ -360,6 +374,169 @@ public sealed class CycloneDxComposer | |||||||
|         return dependencies.Count == 0 ? null : dependencies; |         return dependencies.Count == 0 ? null : dependencies; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private static List<Vulnerability>? BuildVulnerabilities( | ||||||
|  |         SbomCompositionRequest request, | ||||||
|  |         ComponentGraph graph, | ||||||
|  |         ImmutableArray<AggregatedComponent> 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<Vulnerability>(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<Affects> | ||||||
|  |                 { | ||||||
|  |                     new() { Ref = component.Identity.Key } | ||||||
|  |                 }, | ||||||
|  |                 Ratings = ratings, | ||||||
|  |                 Properties = properties, | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             vulnerabilities.Add(vulnerability); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return vulnerabilities.Count == 0 ? null : vulnerabilities; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static List<Rating>? BuildRatings(double score) | ||||||
|  |     { | ||||||
|  |         if (double.IsNaN(score) || double.IsInfinity(score)) | ||||||
|  |         { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return new List<Rating> | ||||||
|  |         { | ||||||
|  |             new() | ||||||
|  |             { | ||||||
|  |                 Method = ScoreMethod.Other, | ||||||
|  |                 Justification = "StellaOps Policy score", | ||||||
|  |                 Score = score, | ||||||
|  |                 Severity = Severity.Unknown, | ||||||
|  |                 Source = new Source { Name = "StellaOps.Policy" }, | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static List<Property>? BuildVulnerabilityProperties(SbomPolicyFinding finding) | ||||||
|  |     { | ||||||
|  |         var properties = new List<Property>(); | ||||||
|  |  | ||||||
|  |         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<Property> 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<Property> 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<Property> 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<Property> properties, string name, double? value) | ||||||
|  |     { | ||||||
|  |         if (!value.HasValue) | ||||||
|  |         { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         AddDoubleProperty(properties, name, value.Value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private static Component.Classification MapClassification(string? type) |     private static Component.Classification MapClassification(string? type) | ||||||
|     { |     { | ||||||
|         if (string.IsNullOrWhiteSpace(type)) |         if (string.IsNullOrWhiteSpace(type)) | ||||||
| @@ -372,7 +549,7 @@ public sealed class CycloneDxComposer | |||||||
|             "application" => Component.Classification.Application, |             "application" => Component.Classification.Application, | ||||||
|             "framework" => Component.Classification.Framework, |             "framework" => Component.Classification.Framework, | ||||||
|             "container" => Component.Classification.Container, |             "container" => Component.Classification.Container, | ||||||
|             "operating-system" or "os" => Component.Classification.OperationSystem, |             "operating-system" or "os" => Component.Classification.Operating_System, | ||||||
|             "device" => Component.Classification.Device, |             "device" => Component.Classification.Device, | ||||||
|             "firmware" => Component.Classification.Firmware, |             "firmware" => Component.Classification.Firmware, | ||||||
|             "file" => Component.Classification.File, |             "file" => Component.Classification.File, | ||||||
| @@ -396,6 +573,9 @@ public sealed class CycloneDxComposer | |||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private static string FormatDouble(double value) | ||||||
|  |         => value.ToString("0.############################", CultureInfo.InvariantCulture); | ||||||
|  |  | ||||||
|     private static string ComputeSha256(byte[] bytes) |     private static string ComputeSha256(byte[] bytes) | ||||||
|     { |     { | ||||||
|         using var sha256 = SHA256.Create(); |         using var sha256 = SHA256.Create(); | ||||||
|   | |||||||
| @@ -42,13 +42,17 @@ public sealed record SbomCompositionRequest | |||||||
|     public IReadOnlyDictionary<string, string>? AdditionalProperties { get; init; } |     public IReadOnlyDictionary<string, string>? AdditionalProperties { get; init; } | ||||||
|         = null; |         = null; | ||||||
|  |  | ||||||
|  |     public ImmutableArray<SbomPolicyFinding> PolicyFindings { get; init; } | ||||||
|  |         = ImmutableArray<SbomPolicyFinding>.Empty; | ||||||
|  |  | ||||||
|     public static SbomCompositionRequest Create( |     public static SbomCompositionRequest Create( | ||||||
|         ImageArtifactDescriptor image, |         ImageArtifactDescriptor image, | ||||||
|         IEnumerable<LayerComponentFragment> fragments, |         IEnumerable<LayerComponentFragment> fragments, | ||||||
|         DateTimeOffset generatedAt, |         DateTimeOffset generatedAt, | ||||||
|         string? generatorName = null, |         string? generatorName = null, | ||||||
|         string? generatorVersion = null, |         string? generatorVersion = null, | ||||||
|         IReadOnlyDictionary<string, string>? properties = null) |         IReadOnlyDictionary<string, string>? properties = null, | ||||||
|  |         IEnumerable<SbomPolicyFinding>? policyFindings = null) | ||||||
|     { |     { | ||||||
|         ArgumentNullException.ThrowIfNull(image); |         ArgumentNullException.ThrowIfNull(image); | ||||||
|         ArgumentNullException.ThrowIfNull(fragments); |         ArgumentNullException.ThrowIfNull(fragments); | ||||||
| @@ -70,6 +74,7 @@ public sealed record SbomCompositionRequest | |||||||
|             GeneratorName = Normalize(generatorName), |             GeneratorName = Normalize(generatorName), | ||||||
|             GeneratorVersion = Normalize(generatorVersion), |             GeneratorVersion = Normalize(generatorVersion), | ||||||
|             AdditionalProperties = properties, |             AdditionalProperties = properties, | ||||||
|  |             PolicyFindings = NormalizePolicyFindings(policyFindings), | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -82,4 +87,50 @@ public sealed record SbomCompositionRequest | |||||||
|  |  | ||||||
|         return value.Trim(); |         return value.Trim(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private static ImmutableArray<SbomPolicyFinding> NormalizePolicyFindings(IEnumerable<SbomPolicyFinding>? policyFindings) | ||||||
|  |     { | ||||||
|  |         if (policyFindings is null) | ||||||
|  |         { | ||||||
|  |             return ImmutableArray<SbomPolicyFinding>.Empty; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var builder = ImmutableArray.CreateBuilder<SbomPolicyFinding>(); | ||||||
|  |         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<SbomPolicyFinding>.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(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										65
									
								
								src/StellaOps.Scanner.Emit/Composition/SbomPolicyFinding.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/StellaOps.Scanner.Emit/Composition/SbomPolicyFinding.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<KeyValuePair<string, double>> Inputs { get; init; } = ImmutableArray<KeyValuePair<string, double>>.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<KeyValuePair<string, double>>.Empty | ||||||
|  |             : Inputs | ||||||
|  |                 .Where(static pair => !string.IsNullOrWhiteSpace(pair.Key)) | ||||||
|  |                 .Select(static pair => new KeyValuePair<string, double>(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 | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -12,7 +12,7 @@ | |||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="CycloneDX.Core" Version="5.1.0" /> |     <PackageReference Include="CycloneDX.Core" Version="10.0.1" /> | ||||||
|     <PackageReference Include="RoaringBitmap" Version="0.0.9" /> |     <PackageReference Include="RoaringBitmap" Version="0.0.9" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -2,11 +2,11 @@ | |||||||
|  |  | ||||||
| | ID | Status | Owner(s) | Depends on | Description | Exit Criteria | | | 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-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 | 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-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 | 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-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 | 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-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 | 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-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 | 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-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-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. | | ||||||
|   | |||||||
| @@ -61,7 +61,8 @@ public static class TelemetryExtensions | |||||||
|                 metrics |                 metrics | ||||||
|                     .AddMeter( |                     .AddMeter( | ||||||
|                         ScannerWorkerInstrumentation.MeterName, |                         ScannerWorkerInstrumentation.MeterName, | ||||||
|                         "StellaOps.Scanner.Analyzers.Lang.Node") |                         "StellaOps.Scanner.Analyzers.Lang.Node", | ||||||
|  |                         "StellaOps.Scanner.Analyzers.Lang.Go") | ||||||
|                     .AddRuntimeInstrumentation() |                     .AddRuntimeInstrumentation() | ||||||
|                     .AddProcessInstrumentation(); |                     .AddProcessInstrumentation(); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user