Add inline DSSE provenance documentation and Mongo schema

- Introduced a new document outlining the inline DSSE provenance for SBOM, VEX, scan, and derived events.
- Defined the Mongo schema for event patches, including key fields for provenance and trust verification.
- Documented the write path for ingesting provenance metadata and backfilling historical events.
- Created CI/CD snippets for uploading DSSE attestations and generating provenance metadata.
- Established Mongo indexes for efficient provenance queries and provided query recipes for various use cases.
- Outlined policy gates for managing VEX decisions based on provenance verification.
- Included UI nudges for displaying provenance information and implementation tasks for future enhancements.

---

Implement reachability lattice and scoring model

- Developed a comprehensive document detailing the reachability lattice and scoring model.
- Defined core types for reachability states, evidence, and mitigations with corresponding C# models.
- Established a scoring policy with base score contributions from various evidence classes.
- Mapped reachability states to VEX gates and provided a clear overview of evidence sources.
- Documented the event graph schema for persisting reachability data in MongoDB.
- Outlined the integration of runtime probes for evidence collection and defined a roadmap for future tasks.

---

Introduce uncertainty states and entropy scoring

- Created a draft document for tracking uncertainty states and their impact on risk scoring.
- Defined core uncertainty states with associated entropy values and evidence requirements.
- Established a schema for storing uncertainty states alongside findings.
- Documented the risk score calculation incorporating uncertainty and its effect on final risk assessments.
- Provided policy guidelines for handling uncertainty in decision-making processes.
- Outlined UI guidelines for displaying uncertainty information and suggested remediation actions.

---

Add Ruby package inventory management

- Implemented Ruby package inventory management with corresponding data models and storage mechanisms.
- Created C# records for Ruby package inventory, artifacts, provenance, and runtime details.
- Developed a repository for managing Ruby package inventory documents in MongoDB.
- Implemented a service for storing and retrieving Ruby package inventories.
- Added unit tests for the Ruby package inventory store to ensure functionality and data integrity.
This commit is contained in:
master
2025-11-13 00:20:33 +02:00
parent 86be324fc0
commit 7040984215
41 changed files with 1955 additions and 76 deletions

View File

@@ -10,5 +10,6 @@
<ItemGroup>
<ProjectReference Include="../../StellaOps.Scanner.Worker/StellaOps.Scanner.Worker.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Queue/StellaOps.Scanner.Queue.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang.Ruby/StellaOps.Scanner.Analyzers.Lang.Ruby.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
@@ -11,6 +12,8 @@ using System.Threading.Tasks;
using System.Security.Cryptography;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Scanner.Analyzers.Lang;
using StellaOps.Scanner.Analyzers.Lang.Ruby;
using StellaOps.Scanner.Core.Contracts;
using StellaOps.Scanner.EntryTrace;
using StellaOps.Scanner.Surface.Env;
@@ -44,7 +47,8 @@ public sealed class SurfaceManifestStageExecutorTests
environment,
metrics,
NullLogger<SurfaceManifestStageExecutor>.Instance,
hash);
hash,
new NullRubyPackageInventoryStore());
var context = CreateContext();
@@ -80,7 +84,8 @@ public sealed class SurfaceManifestStageExecutorTests
environment,
metrics,
NullLogger<SurfaceManifestStageExecutor>.Instance,
hash);
hash,
new NullRubyPackageInventoryStore());
var context = CreateContext();
PopulateAnalysis(context);
@@ -158,6 +163,69 @@ public sealed class SurfaceManifestStageExecutorTests
context.Analysis.Set(ScanAnalysisKeys.LayerComponentFragments, ImmutableArray.Create(fragment));
}
[Fact]
public async Task ExecuteAsync_PersistsRubyPackageInventoryWhenResultsExist()
{
var metrics = new ScannerWorkerMetrics();
var publisher = new TestSurfaceManifestPublisher("tenant-a");
var cache = new RecordingSurfaceCache();
var environment = new TestSurfaceEnvironment("tenant-a");
var hash = CreateCryptoHash();
var packageStore = new RecordingRubyPackageStore();
var executor = new SurfaceManifestStageExecutor(
publisher,
cache,
environment,
metrics,
NullLogger<SurfaceManifestStageExecutor>.Instance,
hash,
packageStore);
var context = CreateContext();
PopulateAnalysis(context);
await PopulateRubyAnalyzerResultsAsync(context);
await executor.ExecuteAsync(context, CancellationToken.None);
Assert.NotNull(packageStore.LastInventory);
Assert.Equal(context.ScanId, packageStore.LastInventory!.ScanId);
Assert.NotEmpty(packageStore.LastInventory!.Packages);
}
private static async Task PopulateRubyAnalyzerResultsAsync(ScanJobContext context)
{
var fixturePath = Path.Combine(
ResolveRepositoryRoot(),
"src",
"Scanner",
"__Tests",
"StellaOps.Scanner.Analyzers.Lang.Ruby.Tests",
"Fixtures",
"lang",
"ruby",
"simple-app");
var analyzer = new RubyLanguageAnalyzer();
var engine = new LanguageAnalyzerEngine(new ILanguageAnalyzer[] { analyzer });
var analyzerContext = new LanguageAnalyzerContext(
fixturePath,
TimeProvider.System,
usageHints: null,
services: null,
analysisStore: context.Analysis);
var result = await engine.AnalyzeAsync(analyzerContext, CancellationToken.None);
var dictionary = new Dictionary<string, LanguageAnalyzerResult>(StringComparer.OrdinalIgnoreCase)
{
["ruby"] = result
};
context.Analysis.Set(
ScanAnalysisKeys.LanguageAnalyzerResults,
new ReadOnlyDictionary<string, LanguageAnalyzerResult>(dictionary));
}
[Fact]
public async Task ExecuteAsync_IncludesDenoObservationPayloadWhenPresent()
{
@@ -172,7 +240,8 @@ public sealed class SurfaceManifestStageExecutorTests
environment,
metrics,
NullLogger<SurfaceManifestStageExecutor>.Instance,
hash);
hash,
new NullRubyPackageInventoryStore());
var context = CreateContext();
var observationBytes = Encoding.UTF8.GetBytes("{\"entrypoints\":[\"mod.ts\"]}");
@@ -390,6 +459,36 @@ public sealed class SurfaceManifestStageExecutorTests
}
}
private sealed class RecordingRubyPackageStore : IRubyPackageInventoryStore
{
public RubyPackageInventory? LastInventory { get; private set; }
public Task StoreAsync(RubyPackageInventory inventory, CancellationToken cancellationToken)
{
LastInventory = inventory;
return Task.CompletedTask;
}
public Task<RubyPackageInventory?> GetAsync(string scanId, CancellationToken cancellationToken)
=> Task.FromResult(LastInventory);
}
private static string ResolveRepositoryRoot()
{
var directory = AppContext.BaseDirectory;
while (!string.IsNullOrWhiteSpace(directory))
{
if (Directory.Exists(Path.Combine(directory, ".git")))
{
return directory;
}
directory = Path.GetDirectoryName(directory) ?? string.Empty;
}
throw new InvalidOperationException("Repository root not found.");
}
private sealed class FakeJobLease : IScanJobLease
{
private readonly Dictionary<string, string> _metadata = new()