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
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:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 =>
|
||||
{
|
||||
|
||||
@@ -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. |
|
||||
|
||||
Reference in New Issue
Block a user