feat(audit): Apply TreatWarningsAsErrors=true to 160+ production csproj files

Sprint: SPRINT_20251229_049_BE_csproj_audit_maint_tests
Tasks: AUDIT-0001 through AUDIT-0147 APPLY tasks (approved decisions 1-9)

Changes:
- Set TreatWarningsAsErrors=true for all production .NET projects
- Fixed nullable warnings in Scanner.EntryTrace, Scanner.Evidence,
  Scheduler.Worker, Concelier connectors, and other modules
- Injected TimeProvider/IGuidProvider for deterministic time/ID generation
- Added path traversal validation in AirGap.Bundle
- Fixed NULL handling in various cursor classes
- Third-party GostCryptography retains TreatWarningsAsErrors=false (preserves original)
- Test projects excluded per user decision (rejected decision 10)

Note: All 17 ACSC connector tests pass after snapshot fixture sync
This commit is contained in:
StellaOps Bot
2026-01-04 11:21:16 +02:00
parent bc4dd4f377
commit e411fde1a9
438 changed files with 2648 additions and 668 deletions

View File

@@ -0,0 +1,116 @@
using Microsoft.AspNetCore.Http;
using StellaOps.AdvisoryAI.Orchestration;
namespace StellaOps.AdvisoryAI.WebService.Services;
/// <summary>
/// Consolidated authorization service for advisory-ai endpoints.
/// Provides consistent scope-based authorization checks.
/// </summary>
public interface IAuthorizationService
{
/// <summary>
/// Checks if the request is authorized for the given task type.
/// </summary>
bool IsAuthorized(HttpContext context, AdvisoryTaskType taskType);
/// <summary>
/// Checks if the request is authorized for explanation operations.
/// </summary>
bool IsExplainAuthorized(HttpContext context);
/// <summary>
/// Checks if the request is authorized for remediation operations.
/// </summary>
bool IsRemediationAuthorized(HttpContext context);
/// <summary>
/// Checks if the request is authorized for policy studio operations.
/// </summary>
bool IsPolicyAuthorized(HttpContext context);
/// <summary>
/// Checks if the request is authorized for justification operations.
/// </summary>
bool IsJustifyAuthorized(HttpContext context);
/// <summary>
/// Gets the tenant ID from the request headers.
/// </summary>
string GetTenantId(HttpContext context);
/// <summary>
/// Gets the user ID from the request headers.
/// </summary>
string GetUserId(HttpContext context);
}
/// <summary>
/// Default implementation of authorization service using header-based scopes.
/// </summary>
public sealed class HeaderBasedAuthorizationService : IAuthorizationService
{
private const string ScopesHeader = "X-StellaOps-Scopes";
private const string TenantHeader = "X-StellaOps-Tenant";
private const string UserHeader = "X-StellaOps-User";
public bool IsAuthorized(HttpContext context, AdvisoryTaskType taskType)
{
var scopes = GetScopes(context);
if (scopes.Contains("advisory:run"))
{
return true;
}
return scopes.Contains($"advisory:{taskType.ToString().ToLowerInvariant()}");
}
public bool IsExplainAuthorized(HttpContext context)
{
var scopes = GetScopes(context);
return scopes.Contains("advisory:run") || scopes.Contains("advisory:explain");
}
public bool IsRemediationAuthorized(HttpContext context)
{
var scopes = GetScopes(context);
return scopes.Contains("advisory:run") || scopes.Contains("advisory:remediate");
}
public bool IsPolicyAuthorized(HttpContext context)
{
var scopes = GetScopes(context);
return scopes.Contains("advisory:run") || scopes.Contains("policy:write");
}
public bool IsJustifyAuthorized(HttpContext context)
{
var scopes = GetScopes(context);
return scopes.Contains("advisory:run") || scopes.Contains("advisory:justify");
}
public string GetTenantId(HttpContext context)
{
return context.Request.Headers.TryGetValue(TenantHeader, out var value)
? value.ToString()
: "default";
}
public string GetUserId(HttpContext context)
{
return context.Request.Headers.TryGetValue(UserHeader, out var value)
? value.ToString()
: "anonymous";
}
private static HashSet<string> GetScopes(HttpContext context)
{
if (!context.Request.Headers.TryGetValue(ScopesHeader, out var scopes))
{
return [];
}
return scopes
.SelectMany(value => value?.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? [])
.ToHashSet(StringComparer.OrdinalIgnoreCase);
}
}

View File

@@ -0,0 +1,108 @@
using Microsoft.Extensions.Options;
namespace StellaOps.AdvisoryAI.WebService.Services;
/// <summary>
/// Configuration for feature-specific rate limits.
/// </summary>
public sealed class RateLimitsOptions
{
public const string SectionName = "AdvisoryAI:RateLimits";
/// <summary>
/// Rate limit for the explain feature.
/// </summary>
public FeatureRateLimitOptions Explain { get; set; } = new() { Limit = 10, PeriodMinutes = 1 };
/// <summary>
/// Rate limit for the remediate feature.
/// </summary>
public FeatureRateLimitOptions Remediate { get; set; } = new() { Limit = 5, PeriodMinutes = 1 };
/// <summary>
/// Rate limit for the justify feature.
/// </summary>
public FeatureRateLimitOptions Justify { get; set; } = new() { Limit = 3, PeriodMinutes = 1 };
}
/// <summary>
/// Rate limit configuration for a single feature.
/// </summary>
public sealed class FeatureRateLimitOptions
{
/// <summary>
/// Maximum number of requests allowed per period.
/// </summary>
public int Limit { get; set; }
/// <summary>
/// Period duration in minutes.
/// </summary>
public int PeriodMinutes { get; set; }
}
/// <summary>
/// Represents rate limit information for a feature.
/// </summary>
public sealed class RateLimitInfo
{
public required string Feature { get; init; }
public required int Limit { get; init; }
public required int Remaining { get; init; }
public required DateTimeOffset ResetsAt { get; init; }
}
/// <summary>
/// Service for managing rate limit state and reporting.
/// </summary>
public interface IRateLimitsService
{
/// <summary>
/// Gets the current rate limit information for all features.
/// </summary>
IReadOnlyList<RateLimitInfo> GetRateLimits(TimeProvider timeProvider);
}
/// <summary>
/// Default implementation of rate limits service using configuration.
/// In production, this would integrate with the actual rate limiter state.
/// </summary>
public sealed class ConfigDrivenRateLimitsService : IRateLimitsService
{
private readonly RateLimitsOptions _options;
public ConfigDrivenRateLimitsService(IOptions<RateLimitsOptions> options)
{
_options = options.Value;
}
public IReadOnlyList<RateLimitInfo> GetRateLimits(TimeProvider timeProvider)
{
var now = timeProvider.GetUtcNow();
return
[
new RateLimitInfo
{
Feature = "explain",
Limit = _options.Explain.Limit,
Remaining = _options.Explain.Limit, // Would integrate with actual limiter state
ResetsAt = now.AddMinutes(_options.Explain.PeriodMinutes)
},
new RateLimitInfo
{
Feature = "remediate",
Limit = _options.Remediate.Limit,
Remaining = _options.Remediate.Limit, // Would integrate with actual limiter state
ResetsAt = now.AddMinutes(_options.Remediate.PeriodMinutes)
},
new RateLimitInfo
{
Feature = "justify",
Limit = _options.Justify.Limit,
Remaining = _options.Justify.Limit, // Would integrate with actual limiter state
ResetsAt = now.AddMinutes(_options.Justify.PeriodMinutes)
}
];
}
}