Add SBOM, symbols, traces, and VEX files for CVE-2022-21661 SQLi case
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Created CycloneDX and SPDX SBOM files for both reachable and unreachable images.
- Added symbols.json detailing function entry and sink points in the WordPress code.
- Included runtime traces for function calls in both reachable and unreachable scenarios.
- Developed OpenVEX files indicating vulnerability status and justification for both cases.
- Updated README for evaluator harness to guide integration with scanner output.
This commit is contained in:
master
2025-11-08 20:53:45 +02:00
parent 515975edc5
commit 536f6249a6
837 changed files with 37279 additions and 14675 deletions

View File

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using StellaOps.Configuration;
using StellaOps.Scanner.Core.Contracts;
namespace StellaOps.Scanner.Worker.Options;
@@ -17,13 +18,15 @@ public sealed class ScannerWorkerOptions
public PollingOptions Polling { get; } = new();
public AuthorityOptions Authority { get; } = new();
public AuthorityOptions Authority { get; } = new();
public TelemetryOptions Telemetry { get; } = new();
public ShutdownOptions Shutdown { get; } = new();
public AnalyzerOptions Analyzers { get; } = new();
public StellaOpsCryptoOptions Crypto { get; } = new();
public sealed class QueueOptions
{

View File

@@ -1,15 +1,14 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Scanner.Analyzers.Lang;
using StellaOps.Scanner.Analyzers.Lang;
using StellaOps.Scanner.Analyzers.Lang.Internal;
using StellaOps.Scanner.Analyzers.Lang.Plugin;
using StellaOps.Scanner.Analyzers.OS;
@@ -22,9 +21,9 @@ using StellaOps.Scanner.Surface.FS;
using StellaOps.Scanner.Surface.Validation;
using StellaOps.Scanner.Worker.Options;
using StellaOps.Scanner.Worker.Diagnostics;
namespace StellaOps.Scanner.Worker.Processing;
namespace StellaOps.Scanner.Worker.Processing;
internal sealed class CompositeScanAnalyzerDispatcher : IScanAnalyzerDispatcher
{
private readonly IServiceScopeFactory _scopeFactory;
@@ -32,17 +31,19 @@ internal sealed class CompositeScanAnalyzerDispatcher : IScanAnalyzerDispatcher
private readonly ILanguageAnalyzerPluginCatalog _languageCatalog;
private readonly ScannerWorkerOptions _options;
private readonly ILogger<CompositeScanAnalyzerDispatcher> _logger;
private readonly ICryptoHash _hash;
private readonly ScannerWorkerMetrics _metrics;
private IReadOnlyList<string> _osPluginDirectories = Array.Empty<string>();
private IReadOnlyList<string> _languagePluginDirectories = Array.Empty<string>();
public CompositeScanAnalyzerDispatcher(
IServiceScopeFactory scopeFactory,
IOSAnalyzerPluginCatalog osCatalog,
private IReadOnlyList<string> _osPluginDirectories = Array.Empty<string>();
private IReadOnlyList<string> _languagePluginDirectories = Array.Empty<string>();
public CompositeScanAnalyzerDispatcher(
IServiceScopeFactory scopeFactory,
IOSAnalyzerPluginCatalog osCatalog,
ILanguageAnalyzerPluginCatalog languageCatalog,
IOptions<ScannerWorkerOptions> options,
ILogger<CompositeScanAnalyzerDispatcher> logger,
ScannerWorkerMetrics metrics)
ScannerWorkerMetrics metrics,
ICryptoHash hash)
{
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
_osCatalog = osCatalog ?? throw new ArgumentNullException(nameof(osCatalog));
@@ -50,97 +51,98 @@ internal sealed class CompositeScanAnalyzerDispatcher : IScanAnalyzerDispatcher
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_metrics = metrics ?? throw new ArgumentNullException(nameof(metrics));
LoadPlugins();
}
public async ValueTask ExecuteAsync(ScanJobContext context, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
using var scope = _scopeFactory.CreateScope();
var services = scope.ServiceProvider;
var osAnalyzers = _osCatalog.CreateAnalyzers(services);
var languageAnalyzers = _languageCatalog.CreateAnalyzers(services);
if (osAnalyzers.Count == 0 && languageAnalyzers.Count == 0)
{
_logger.LogWarning("No analyzer plug-ins available; skipping analyzer stage for job {JobId}.", context.JobId);
return;
}
var metadata = new Dictionary<string, string>(context.Lease.Metadata, StringComparer.Ordinal);
var rootfsPath = ResolvePath(metadata, _options.Analyzers.RootFilesystemMetadataKey);
var workspacePath = ResolvePath(metadata, _options.Analyzers.WorkspaceMetadataKey) ?? rootfsPath;
if (osAnalyzers.Count > 0)
{
await ExecuteOsAnalyzersAsync(context, osAnalyzers, services, rootfsPath, workspacePath, cancellationToken)
.ConfigureAwait(false);
}
if (languageAnalyzers.Count > 0)
{
await ExecuteLanguageAnalyzersAsync(context, languageAnalyzers, services, workspacePath, cancellationToken)
.ConfigureAwait(false);
}
}
private async Task ExecuteOsAnalyzersAsync(
ScanJobContext context,
IReadOnlyList<IOSPackageAnalyzer> analyzers,
IServiceProvider services,
string? rootfsPath,
string? workspacePath,
CancellationToken cancellationToken)
{
if (rootfsPath is null)
{
_logger.LogWarning(
"Metadata key '{MetadataKey}' missing for job {JobId}; unable to locate root filesystem. OS analyzers skipped.",
_options.Analyzers.RootFilesystemMetadataKey,
context.JobId);
return;
}
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
var results = new List<OSPackageAnalyzerResult>(analyzers.Count);
foreach (var analyzer in analyzers)
{
cancellationToken.ThrowIfCancellationRequested();
var analyzerLogger = loggerFactory.CreateLogger(analyzer.GetType());
var analyzerContext = new OSPackageAnalyzerContext(rootfsPath, workspacePath, context.TimeProvider, analyzerLogger, context.Lease.Metadata);
try
{
var result = await analyzer.AnalyzeAsync(analyzerContext, cancellationToken).ConfigureAwait(false);
results.Add(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Analyzer {AnalyzerId} failed for job {JobId}.", analyzer.AnalyzerId, context.JobId);
}
}
if (results.Count == 0)
{
return;
}
var dictionary = results.ToDictionary(result => result.AnalyzerId, StringComparer.OrdinalIgnoreCase);
context.Analysis.Set(ScanAnalysisKeys.OsPackageAnalyzers, dictionary);
var fragments = OsComponentMapper.ToLayerFragments(results);
if (!fragments.IsDefaultOrEmpty)
{
context.Analysis.AppendLayerFragments(fragments);
context.Analysis.Set(ScanAnalysisKeys.OsComponentFragments, fragments);
}
}
_hash = hash ?? throw new ArgumentNullException(nameof(hash));
LoadPlugins();
}
public async ValueTask ExecuteAsync(ScanJobContext context, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
using var scope = _scopeFactory.CreateScope();
var services = scope.ServiceProvider;
var osAnalyzers = _osCatalog.CreateAnalyzers(services);
var languageAnalyzers = _languageCatalog.CreateAnalyzers(services);
if (osAnalyzers.Count == 0 && languageAnalyzers.Count == 0)
{
_logger.LogWarning("No analyzer plug-ins available; skipping analyzer stage for job {JobId}.", context.JobId);
return;
}
var metadata = new Dictionary<string, string>(context.Lease.Metadata, StringComparer.Ordinal);
var rootfsPath = ResolvePath(metadata, _options.Analyzers.RootFilesystemMetadataKey);
var workspacePath = ResolvePath(metadata, _options.Analyzers.WorkspaceMetadataKey) ?? rootfsPath;
if (osAnalyzers.Count > 0)
{
await ExecuteOsAnalyzersAsync(context, osAnalyzers, services, rootfsPath, workspacePath, cancellationToken)
.ConfigureAwait(false);
}
if (languageAnalyzers.Count > 0)
{
await ExecuteLanguageAnalyzersAsync(context, languageAnalyzers, services, workspacePath, cancellationToken)
.ConfigureAwait(false);
}
}
private async Task ExecuteOsAnalyzersAsync(
ScanJobContext context,
IReadOnlyList<IOSPackageAnalyzer> analyzers,
IServiceProvider services,
string? rootfsPath,
string? workspacePath,
CancellationToken cancellationToken)
{
if (rootfsPath is null)
{
_logger.LogWarning(
"Metadata key '{MetadataKey}' missing for job {JobId}; unable to locate root filesystem. OS analyzers skipped.",
_options.Analyzers.RootFilesystemMetadataKey,
context.JobId);
return;
}
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
var results = new List<OSPackageAnalyzerResult>(analyzers.Count);
foreach (var analyzer in analyzers)
{
cancellationToken.ThrowIfCancellationRequested();
var analyzerLogger = loggerFactory.CreateLogger(analyzer.GetType());
var analyzerContext = new OSPackageAnalyzerContext(rootfsPath, workspacePath, context.TimeProvider, analyzerLogger, context.Lease.Metadata);
try
{
var result = await analyzer.AnalyzeAsync(analyzerContext, cancellationToken).ConfigureAwait(false);
results.Add(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Analyzer {AnalyzerId} failed for job {JobId}.", analyzer.AnalyzerId, context.JobId);
}
}
if (results.Count == 0)
{
return;
}
var dictionary = results.ToDictionary(result => result.AnalyzerId, StringComparer.OrdinalIgnoreCase);
context.Analysis.Set(ScanAnalysisKeys.OsPackageAnalyzers, dictionary);
var fragments = OsComponentMapper.ToLayerFragments(results);
if (!fragments.IsDefaultOrEmpty)
{
context.Analysis.AppendLayerFragments(fragments);
context.Analysis.Set(ScanAnalysisKeys.OsComponentFragments, fragments);
}
}
private async Task ExecuteLanguageAnalyzersAsync(
ScanJobContext context,
IReadOnlyList<ILanguageAnalyzer> analyzers,
@@ -189,7 +191,7 @@ internal sealed class CompositeScanAnalyzerDispatcher : IScanAnalyzerDispatcher
context.JobId);
var fallbackBytes = Encoding.UTF8.GetBytes(workspacePath);
workspaceFingerprint = Convert.ToHexString(SHA256.HashData(fallbackBytes)).ToLowerInvariant();
workspaceFingerprint = _hash.ComputeHashHex(fallbackBytes, HashAlgorithms.Sha256);
}
var cache = services.GetRequiredService<ISurfaceCache>();
@@ -261,85 +263,85 @@ internal sealed class CompositeScanAnalyzerDispatcher : IScanAnalyzerDispatcher
context.Analysis.Set(ScanAnalysisKeys.LanguageComponentFragments, immutableFragments);
}
}
private void LoadPlugins()
{
_osPluginDirectories = NormalizeDirectories(_options.Analyzers.PluginDirectories, Path.Combine("plugins", "scanner", "analyzers", "os"));
for (var i = 0; i < _osPluginDirectories.Count; i++)
{
var directory = _osPluginDirectories[i];
var seal = i == _osPluginDirectories.Count - 1;
try
{
_osCatalog.LoadFromDirectory(directory, seal);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load OS analyzer plug-ins from {Directory}.", directory);
}
}
_languagePluginDirectories = NormalizeDirectories(_options.Analyzers.LanguagePluginDirectories, Path.Combine("plugins", "scanner", "analyzers", "lang"));
for (var i = 0; i < _languagePluginDirectories.Count; i++)
{
var directory = _languagePluginDirectories[i];
var seal = i == _languagePluginDirectories.Count - 1;
try
{
_languageCatalog.LoadFromDirectory(directory, seal);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load language analyzer plug-ins from {Directory}.", directory);
}
}
}
private static IReadOnlyList<string> NormalizeDirectories(IEnumerable<string> configured, string fallbackRelative)
{
var directories = new List<string>();
foreach (var configuredPath in configured ?? Array.Empty<string>())
{
if (string.IsNullOrWhiteSpace(configuredPath))
{
continue;
}
var path = configuredPath;
if (!Path.IsPathRooted(path))
{
path = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, path));
}
directories.Add(path);
}
if (directories.Count == 0)
{
var fallback = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, fallbackRelative));
directories.Add(fallback);
}
return new ReadOnlyCollection<string>(directories);
}
private static string? ResolvePath(IReadOnlyDictionary<string, string> metadata, string key)
{
if (string.IsNullOrWhiteSpace(key))
{
return null;
}
if (!metadata.TryGetValue(key, out var value) || string.IsNullOrWhiteSpace(value))
{
return null;
}
var trimmed = value.Trim();
return Path.IsPathRooted(trimmed)
? trimmed
: Path.GetFullPath(trimmed);
}
}
private void LoadPlugins()
{
_osPluginDirectories = NormalizeDirectories(_options.Analyzers.PluginDirectories, Path.Combine("plugins", "scanner", "analyzers", "os"));
for (var i = 0; i < _osPluginDirectories.Count; i++)
{
var directory = _osPluginDirectories[i];
var seal = i == _osPluginDirectories.Count - 1;
try
{
_osCatalog.LoadFromDirectory(directory, seal);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load OS analyzer plug-ins from {Directory}.", directory);
}
}
_languagePluginDirectories = NormalizeDirectories(_options.Analyzers.LanguagePluginDirectories, Path.Combine("plugins", "scanner", "analyzers", "lang"));
for (var i = 0; i < _languagePluginDirectories.Count; i++)
{
var directory = _languagePluginDirectories[i];
var seal = i == _languagePluginDirectories.Count - 1;
try
{
_languageCatalog.LoadFromDirectory(directory, seal);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load language analyzer plug-ins from {Directory}.", directory);
}
}
}
private static IReadOnlyList<string> NormalizeDirectories(IEnumerable<string> configured, string fallbackRelative)
{
var directories = new List<string>();
foreach (var configuredPath in configured ?? Array.Empty<string>())
{
if (string.IsNullOrWhiteSpace(configuredPath))
{
continue;
}
var path = configuredPath;
if (!Path.IsPathRooted(path))
{
path = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, path));
}
directories.Add(path);
}
if (directories.Count == 0)
{
var fallback = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, fallbackRelative));
directories.Add(fallback);
}
return new ReadOnlyCollection<string>(directories);
}
private static string? ResolvePath(IReadOnlyDictionary<string, string> metadata, string key)
{
if (string.IsNullOrWhiteSpace(key))
{
return null;
}
if (!metadata.TryGetValue(key, out var value) || string.IsNullOrWhiteSpace(value))
{
return null;
}
var trimmed = value.Trim();
return Path.IsPathRooted(trimmed)
? trimmed
: Path.GetFullPath(trimmed);
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -56,6 +55,7 @@ public sealed class EntryTraceExecutionService : IEntryTraceExecutionService
private readonly ISurfaceCache _surfaceCache;
private readonly ISurfaceSecretProvider _surfaceSecrets;
private readonly IServiceProvider _serviceProvider;
private readonly ICryptoHash _hash;
public EntryTraceExecutionService(
IEntryTraceAnalyzer analyzer,
@@ -69,7 +69,8 @@ public sealed class EntryTraceExecutionService : IEntryTraceExecutionService
ISurfaceEnvironment surfaceEnvironment,
ISurfaceCache surfaceCache,
ISurfaceSecretProvider surfaceSecrets,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
ICryptoHash hash)
{
_analyzer = analyzer ?? throw new ArgumentNullException(nameof(analyzer));
_entryTraceOptions = (entryTraceOptions ?? throw new ArgumentNullException(nameof(entryTraceOptions))).Value ?? new EntryTraceAnalyzerOptions();
@@ -83,6 +84,7 @@ public sealed class EntryTraceExecutionService : IEntryTraceExecutionService
_surfaceCache = surfaceCache ?? throw new ArgumentNullException(nameof(surfaceCache));
_surfaceSecrets = surfaceSecrets ?? throw new ArgumentNullException(nameof(surfaceSecrets));
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_hash = hash ?? throw new ArgumentNullException(nameof(hash));
}
public async ValueTask ExecuteAsync(ScanJobContext context, CancellationToken cancellationToken)
@@ -376,7 +378,7 @@ public sealed class EntryTraceExecutionService : IEntryTraceExecutionService
return true;
}
private static SurfaceCacheKey CreateCacheKey(
private SurfaceCacheKey CreateCacheKey(
string imageDigest,
EntryTraceImageContext context,
string tenant,
@@ -390,11 +392,11 @@ public sealed class EntryTraceExecutionService : IEntryTraceExecutionService
builder.Append('|').Append(ComputeEnvironmentFingerprint(context.Context.Environment));
builder.Append('|').Append(optionsFingerprint);
var hash = ComputeSha256(builder.ToString());
return new SurfaceCacheKey(CacheNamespace, tenant, hash);
var fingerprint = ComputeSha256(builder.ToString());
return new SurfaceCacheKey(CacheNamespace, tenant, fingerprint);
}
private static string ComputeOptionsFingerprint(EntryTraceAnalyzerOptions options)
private string ComputeOptionsFingerprint(EntryTraceAnalyzerOptions options)
{
var builder = new StringBuilder();
builder.Append(options.MaxDepth);
@@ -404,7 +406,7 @@ public sealed class EntryTraceExecutionService : IEntryTraceExecutionService
return ComputeSha256(builder.ToString());
}
private static string ComputeEntrypointSignature(EntrypointSpecification specification)
private string ComputeEntrypointSignature(EntrypointSpecification specification)
{
var builder = new StringBuilder();
builder.AppendJoin(',', specification.Entrypoint);
@@ -415,7 +417,7 @@ public sealed class EntryTraceExecutionService : IEntryTraceExecutionService
return ComputeSha256(builder.ToString());
}
private static string ComputeEnvironmentFingerprint(ImmutableDictionary<string, string> environment)
private string ComputeEnvironmentFingerprint(ImmutableDictionary<string, string> environment)
{
if (environment.Count == 0)
{
@@ -431,12 +433,10 @@ public sealed class EntryTraceExecutionService : IEntryTraceExecutionService
return ComputeSha256(builder.ToString());
}
private static string ComputeSha256(string value)
private string ComputeSha256(string value)
{
using var sha = SHA256.Create();
var bytes = Encoding.UTF8.GetBytes(value);
var hash = sha.ComputeHash(bytes);
return Convert.ToHexString(hash).ToLowerInvariant();
return _hash.ComputeHashHex(bytes, HashAlgorithms.Sha256);
}
private static string? ResolvePath(

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -15,6 +14,7 @@ using StellaOps.Scanner.Storage.ObjectStore;
using StellaOps.Scanner.Storage.Repositories;
using StellaOps.Scanner.Storage.Services;
using StellaOps.Scanner.Surface.Env;
using StellaOps.Cryptography;
namespace StellaOps.Scanner.Worker.Processing.Surface;
@@ -58,6 +58,7 @@ internal sealed class SurfaceManifestPublisher : ISurfaceManifestPublisher
private readonly ISurfaceEnvironment _surfaceEnvironment;
private readonly TimeProvider _timeProvider;
private readonly ILogger<SurfaceManifestPublisher> _logger;
private readonly ICryptoHash _hash;
public SurfaceManifestPublisher(
IArtifactObjectStore objectStore,
@@ -66,7 +67,8 @@ internal sealed class SurfaceManifestPublisher : ISurfaceManifestPublisher
IOptions<ScannerStorageOptions> storageOptions,
ISurfaceEnvironment surfaceEnvironment,
TimeProvider timeProvider,
ILogger<SurfaceManifestPublisher> logger)
ILogger<SurfaceManifestPublisher> logger,
ICryptoHash hash)
{
_objectStore = objectStore ?? throw new ArgumentNullException(nameof(objectStore));
_artifactRepository = artifactRepository ?? throw new ArgumentNullException(nameof(artifactRepository));
@@ -75,6 +77,7 @@ internal sealed class SurfaceManifestPublisher : ISurfaceManifestPublisher
_surfaceEnvironment = surfaceEnvironment ?? throw new ArgumentNullException(nameof(surfaceEnvironment));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_hash = hash ?? throw new ArgumentNullException(nameof(hash));
}
public async Task<SurfaceManifestPublishResult> PublishAsync(SurfaceManifestRequest request, CancellationToken cancellationToken)
@@ -245,14 +248,13 @@ internal sealed class SurfaceManifestPublisher : ISurfaceManifestPublisher
_ => format.ToString().ToLowerInvariant()
};
private static string ComputeDigest(ReadOnlySpan<byte> content)
private string ComputeDigest(ReadOnlySpan<byte> content)
{
Span<byte> hash = stackalloc byte[32];
SHA256.HashData(content, hash);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
var hex = _hash.ComputeHashHex(content, HashAlgorithms.Sha256);
return $"sha256:{hex}";
}
private static string ComputeDigest(byte[] content)
private string ComputeDigest(byte[] content)
=> ComputeDigest(content.AsSpan());
private static string NormalizeDigest(string digest)

View File

@@ -3,7 +3,6 @@ using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -15,6 +14,7 @@ using StellaOps.Scanner.Surface.Env;
using StellaOps.Scanner.Surface.FS;
using StellaOps.Scanner.Storage.Catalog;
using StellaOps.Scanner.Worker.Diagnostics;
using StellaOps.Cryptography;
namespace StellaOps.Scanner.Worker.Processing.Surface;
@@ -37,6 +37,7 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor
private readonly ISurfaceEnvironment _surfaceEnvironment;
private readonly ScannerWorkerMetrics _metrics;
private readonly ILogger<SurfaceManifestStageExecutor> _logger;
private readonly ICryptoHash _hash;
private readonly string _componentVersion;
public SurfaceManifestStageExecutor(
@@ -44,13 +45,15 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor
ISurfaceCache surfaceCache,
ISurfaceEnvironment surfaceEnvironment,
ScannerWorkerMetrics metrics,
ILogger<SurfaceManifestStageExecutor> logger)
ILogger<SurfaceManifestStageExecutor> logger,
ICryptoHash hash)
{
_publisher = publisher ?? throw new ArgumentNullException(nameof(publisher));
_surfaceCache = surfaceCache ?? throw new ArgumentNullException(nameof(surfaceCache));
_surfaceEnvironment = surfaceEnvironment ?? throw new ArgumentNullException(nameof(surfaceEnvironment));
_metrics = metrics ?? throw new ArgumentNullException(nameof(metrics));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_hash = hash ?? throw new ArgumentNullException(nameof(hash));
_componentVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown";
}
@@ -274,11 +277,10 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor
return digest.Trim();
}
private static string ComputeDigest(ReadOnlySpan<byte> content)
private string ComputeDigest(ReadOnlySpan<byte> content)
{
Span<byte> hash = stackalloc byte[32];
SHA256.HashData(content, hash);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
var hex = _hash.ComputeHashHex(content, HashAlgorithms.Sha256);
return $"sha256:{hex}";
}
private static readonly IFormatProvider CultureInfoInvariant = System.Globalization.CultureInfo.InvariantCulture;

View File

@@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Auth.Client;
using StellaOps.Configuration;
using StellaOps.Scanner.Cache;
using StellaOps.Scanner.Analyzers.OS.Plugin;
using StellaOps.Scanner.Analyzers.Lang.Plugin;
@@ -71,6 +72,7 @@ builder.Services.AddSingleton<ScannerWorkerHostedService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<ScannerWorkerHostedService>());
var workerOptions = builder.Configuration.GetSection(ScannerWorkerOptions.SectionName).Get<ScannerWorkerOptions>() ?? new ScannerWorkerOptions();
builder.Services.AddStellaOpsCrypto(workerOptions.Crypto);
builder.Services.Configure<HostOptions>(options =>
{

View File

@@ -3,6 +3,7 @@
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|----|--------|----------|------------|-------------|---------------|
| SCAN-REPLAY-186-002 | TODO | Scanner Worker Guild | REPLAY-CORE-185-001 | Enforce deterministic analyzer execution when consuming replay input bundles, emit layer Merkle metadata, and author `docs/modules/scanner/deterministic-execution.md` summarising invariants from `docs/replay/DETERMINISTIC_REPLAY.md` Section 4. | Replay mode analyzers pass determinism tests; new doc merged; integration fixtures updated. |
| SCANNER-CRYPTO-90-001 | DONE (2025-11-08) | Scanner Worker Guild & Security Guild | SEC-CRYPTO-90-005 | Route remaining hashing and digest consumers (Surface pointers, manifest publishers, CAS helpers, Sbomer plugins) through ICryptoHash and the configured provider registry.<br>2025-11-08: Worker EntryTrace service, CAS helpers, and Sbomer plugin now depend on ICryptoHash; Local CAS + manifest writer persisted digests via providers; tests updated with CryptoHashFactory/TestCryptoHash helpers; runtime SHA256 calls removed. | No direct SHA256.Create() usage in worker runtime; constructors accept ICryptoHash; tests updated. |
| SCANNER-SURFACE-01 | DONE (2025-11-06) | Scanner Worker Guild | SURFACE-FS-02 | Persist Surface.FS manifests after analyzer stages, including layer CAS metadata and EntryTrace fragments.<br>2025-11-02: Draft Surface.FS manifests emitted for sample scans; telemetry counters under review.<br>2025-11-06: Resuming with manifest writer abstraction, rotation metadata, and telemetry counters for Surface.FS persistence.<br>2025-11-06 21:05Z: Stage now persists manifest/payload caches, exports metrics to Prometheus/Grafana, and WebService pointer tests validate consumption. | Integration tests prove cache entries exist; telemetry counters exported. |
> 2025-11-05 19:18Z: Bound root directory to resolved Surface.Env settings and added unit coverage around the configurator.
> 2025-11-06 18:45Z: Resuming manifest persistence—planning publisher abstraction refactor, CAS storage wiring, and telemetry/test coverage.
@@ -10,3 +11,4 @@
> 2025-11-06 21:05Z: Completed Surface manifest cache + metrics work; tests/docs updated and task ready to close.
| SCANNER-ENV-01 | TODO (2025-11-06) | Scanner Worker Guild | SURFACE-ENV-02 | Replace ad-hoc environment reads with `StellaOps.Scanner.Surface.Env` helpers for cache roots and CAS endpoints.<br>2025-11-02: Worker bootstrap now resolves cache roots via helper; warning path documented; smoke tests running.<br>2025-11-05 14:55Z: Extending helper usage into cache/secrets configuration, updating worker validator wiring, and drafting docs/tests for new Surface.Env outputs.<br>2025-11-06 17:05Z: README/design docs updated with warning catalogue; startup logging guidance captured for ops runbooks.<br>2025-11-06 07:45Z: Helm/Compose env profiles (dev/stage/prod/airgap/mirror) now seed `SCANNER_SURFACE_*` defaults to keep worker cache roots aligned with Surface.Env helpers.<br>2025-11-06 07:55Z: Paused; pending automation tracked via `DEVOPS-OPENSSL-11-001/002` and Surface.Env test fixtures. | Worker boots with helper; misconfiguration warnings documented; smoke tests updated. |
| SCANNER-SECRETS-01 | DONE (2025-11-06) | Scanner Worker Guild, Security Guild | SURFACE-SECRETS-02 | Adopt `StellaOps.Scanner.Surface.Secrets` for registry/CAS credentials during scan execution.<br>2025-11-02: Surface.Secrets provider wired for CAS token retrieval; integration tests added.<br>2025-11-06: Replaced registry credential plumbing with shared provider, added registry secret stage + metrics, and installed .NET 10 RC2 to validate parser/stage suites via targeted `dotnet test`. | Secrets fetched via shared provider; legacy secret code removed; integration tests cover rotation. |
| SCAN-REACH-201-002 | DOING (2025-11-08) | Scanner Worker Guild | SIGNALS-24-002 | Implement language-aware reachability lifters (JVM/WALA, .NET Roslyn+IL, Go SSA, Node/Deno TS AST, Rust MIR, Swift SIL, shell/binary analyzers) emitting canonical SymbolIDs, CAS-stored callgraphs, and `reachability:*` SBOM tags consumed by Signals + Policy. | Fixture library + unit tests per language; CAS manifests published; SBOM components carry reachability tags; docs updated. |