sprints and audit work

This commit is contained in:
StellaOps Bot
2026-01-07 09:36:16 +02:00
parent 05833e0af2
commit ab364c6032
377 changed files with 64534 additions and 1627 deletions

View File

@@ -191,7 +191,25 @@ public sealed class LanguageAnalyzerSmokeRunner
ValidateManifest(manifest, profile, options.PluginDirectoryName);
var pluginAssemblyPath = Path.Combine(pluginRoot, manifest.EntryPoint.Assembly);
// Validate assembly path to prevent path traversal attacks
var assemblyName = manifest.EntryPoint.Assembly;
if (string.IsNullOrWhiteSpace(assemblyName) ||
Path.IsPathRooted(assemblyName) ||
assemblyName.Contains("..") ||
assemblyName.Contains('\0'))
{
throw new InvalidOperationException(
$"Invalid assembly path in manifest: path traversal or absolute path detected in '{assemblyName}'.");
}
var pluginAssemblyPath = Path.GetFullPath(Path.Combine(pluginRoot, assemblyName));
var normalizedPluginRoot = Path.GetFullPath(pluginRoot);
if (!pluginAssemblyPath.StartsWith(normalizedPluginRoot, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
$"Invalid assembly path in manifest: '{assemblyName}' escapes plugin root directory.");
}
if (!File.Exists(pluginAssemblyPath))
{
throw new FileNotFoundException($"Plug-in assembly '{manifest.EntryPoint.Assembly}' not found under '{pluginRoot}'.", pluginAssemblyPath);

View File

@@ -4,14 +4,29 @@ public static class NotifySmokeCheckApp
{
public static async Task<int> RunAsync(string[] args)
{
using var cts = new CancellationTokenSource();
// Handle Ctrl+C for graceful cancellation
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
cts.Cancel();
Console.Error.WriteLine("[INFO] Cancellation requested...");
};
try
{
var options = NotifySmokeOptions.FromEnvironment(Environment.GetEnvironmentVariable);
var runner = new NotifySmokeCheckRunner(options, Console.WriteLine, Console.Error.WriteLine);
await runner.RunAsync(CancellationToken.None).ConfigureAwait(false);
await runner.RunAsync(cts.Token).ConfigureAwait(false);
Console.WriteLine("[OK] Notify smoke validation completed successfully.");
return 0;
}
catch (OperationCanceledException)
{
Console.Error.WriteLine("[CANCELLED] Operation was cancelled.");
return 130; // Standard exit code for SIGINT
}
catch (Exception ex)
{
Console.Error.WriteLine($"[FAIL] {ex.Message}");

View File

@@ -158,12 +158,18 @@ public sealed record NotifyDeliveryRecord(string Kind, string? Status);
public sealed class NotifySmokeCheckRunner
{
private readonly NotifySmokeOptions _options;
private readonly HttpClient? _httpClient;
private readonly Action<string> _info;
private readonly Action<string> _error;
public NotifySmokeCheckRunner(NotifySmokeOptions options, Action<string>? info = null, Action<string>? error = null)
public NotifySmokeCheckRunner(
NotifySmokeOptions options,
Action<string>? info = null,
Action<string>? error = null,
HttpClient? httpClient = null)
{
_options = options;
_httpClient = httpClient;
_info = info ?? (_ => { });
_error = error ?? (_ => { });
}
@@ -192,25 +198,39 @@ public sealed class NotifySmokeCheckRunner
var deliveriesUrl = BuildDeliveriesUrl(_options.Delivery.BaseUri, sinceThreshold, _options.Delivery.Limit);
_info($"[INFO] Querying Notify deliveries via {deliveriesUrl}.");
using var httpClient = BuildHttpClient(_options.Delivery);
using var response = await GetWithRetriesAsync(httpClient, deliveriesUrl, cancellationToken).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
// Use injected HttpClient if provided, otherwise create one (for standalone tool usage)
var ownedClient = _httpClient is null;
var httpClient = _httpClient ?? BuildHttpClient(_options.Delivery);
try
{
var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
throw new InvalidOperationException($"Notify deliveries request failed with {(int)response.StatusCode} {response.ReasonPhrase}: {body}");
ConfigureHttpClient(httpClient, _options.Delivery);
using var response = await GetWithRetriesAsync(httpClient, deliveriesUrl, cancellationToken).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
throw new InvalidOperationException($"Notify deliveries request failed with {(int)response.StatusCode} {response.ReasonPhrase}: {body}");
}
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
Ensure(!string.IsNullOrWhiteSpace(json), "Notify deliveries response body was empty.");
var deliveries = ParseDeliveries(json);
Ensure(deliveries.Count > 0, "Notify deliveries response did not return any records.");
var missingDeliveryKinds = FindMissingDeliveryKinds(deliveries, _options.ExpectedKinds);
Ensure(missingDeliveryKinds.Count == 0, $"Notify deliveries missing successful records for kinds: {string.Join(", ", missingDeliveryKinds)}");
_info("[INFO] Notify deliveries include the expected scanner events.");
}
finally
{
// Only dispose if we created the client
if (ownedClient)
{
httpClient.Dispose();
}
}
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
Ensure(!string.IsNullOrWhiteSpace(json), "Notify deliveries response body was empty.");
var deliveries = ParseDeliveries(json);
Ensure(deliveries.Count > 0, "Notify deliveries response did not return any records.");
var missingDeliveryKinds = FindMissingDeliveryKinds(deliveries, _options.ExpectedKinds);
Ensure(missingDeliveryKinds.Count == 0, $"Notify deliveries missing successful records for kinds: {string.Join(", ", missingDeliveryKinds)}");
_info("[INFO] Notify deliveries include the expected scanner events.");
}
internal static IReadOnlyList<NotifyDeliveryRecord> ParseDeliveries(string json)
@@ -405,18 +425,31 @@ public sealed class NotifySmokeCheckRunner
return await ConnectionMultiplexer.ConnectAsync(options).ConfigureAwait(false);
}
private HttpClient BuildHttpClient(NotifyDeliveryOptions delivery)
private static HttpClient BuildHttpClient(NotifyDeliveryOptions delivery)
{
var httpClient = new HttpClient
return new HttpClient
{
Timeout = delivery.Timeout,
};
}
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", delivery.Token);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Add(delivery.TenantHeader, delivery.Tenant);
private static void ConfigureHttpClient(HttpClient httpClient, NotifyDeliveryOptions delivery)
{
// Only set headers if not already set (allows injected client to have pre-configured headers)
if (httpClient.DefaultRequestHeaders.Authorization is null)
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", delivery.Token);
}
return httpClient;
if (!httpClient.DefaultRequestHeaders.Accept.Any())
{
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
if (!httpClient.DefaultRequestHeaders.Contains(delivery.TenantHeader))
{
httpClient.DefaultRequestHeaders.Add(delivery.TenantHeader, delivery.Tenant);
}
}
private async Task<HttpResponseMessage> GetWithRetriesAsync(HttpClient httpClient, Uri url, CancellationToken cancellationToken)