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

@@ -2,11 +2,25 @@
using System.Text;
using StellaOps.AirGap.Bundle.Models;
using StellaOps.AirGap.Bundle.Serialization;
using StellaOps.AirGap.Bundle.Services;
namespace StellaOps.AirGap.Bundle.Validation;
public sealed class BundleValidator : IBundleValidator
{
private readonly TimeProvider _timeProvider;
private readonly BundleValidationOptions _options;
public BundleValidator() : this(TimeProvider.System, new BundleValidationOptions())
{
}
public BundleValidator(TimeProvider timeProvider, BundleValidationOptions options)
{
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
public async Task<BundleValidationResult> ValidateAsync(
BundleManifest manifest,
string bundlePath,
@@ -14,6 +28,7 @@ public sealed class BundleValidator : IBundleValidator
{
var errors = new List<BundleValidationError>();
var warnings = new List<BundleValidationWarning>();
var now = _timeProvider.GetUtcNow();
if (manifest.Feeds.Length == 0)
{
@@ -25,9 +40,18 @@ public sealed class BundleValidator : IBundleValidator
errors.Add(new BundleValidationError("CryptoMaterials", "Trust roots required"));
}
// Validate feed digests and paths
foreach (var feed in manifest.Feeds)
{
var filePath = Path.Combine(bundlePath, feed.RelativePath);
// Validate path safety
if (!PathValidation.IsSafeRelativePath(feed.RelativePath))
{
errors.Add(new BundleValidationError("Feeds",
$"Feed {feed.FeedId} has unsafe relative path: {feed.RelativePath}"));
continue;
}
var filePath = PathValidation.SafeCombine(bundlePath, feed.RelativePath);
var result = await VerifyFileDigestAsync(filePath, feed.Digest, ct).ConfigureAwait(false);
if (!result.IsValid)
{
@@ -36,21 +60,75 @@ public sealed class BundleValidator : IBundleValidator
}
}
if (manifest.ExpiresAt.HasValue && manifest.ExpiresAt.Value < DateTimeOffset.UtcNow)
// Validate policy digests if enabled
if (_options.ValidatePolicies)
{
foreach (var policy in manifest.Policies)
{
if (!PathValidation.IsSafeRelativePath(policy.RelativePath))
{
errors.Add(new BundleValidationError("Policies",
$"Policy {policy.PolicyId} has unsafe relative path: {policy.RelativePath}"));
continue;
}
var filePath = PathValidation.SafeCombine(bundlePath, policy.RelativePath);
var result = await VerifyFileDigestAsync(filePath, policy.Digest, ct).ConfigureAwait(false);
if (!result.IsValid)
{
errors.Add(new BundleValidationError("Policies",
$"Policy {policy.PolicyId} digest mismatch: expected {policy.Digest}, got {result.ActualDigest}"));
}
}
}
// Validate crypto material digests if enabled
if (_options.ValidateCryptoMaterials)
{
foreach (var crypto in manifest.CryptoMaterials)
{
if (!PathValidation.IsSafeRelativePath(crypto.RelativePath))
{
errors.Add(new BundleValidationError("CryptoMaterials",
$"Crypto material {crypto.ComponentId} has unsafe relative path: {crypto.RelativePath}"));
continue;
}
var filePath = PathValidation.SafeCombine(bundlePath, crypto.RelativePath);
var result = await VerifyFileDigestAsync(filePath, crypto.Digest, ct).ConfigureAwait(false);
if (!result.IsValid)
{
errors.Add(new BundleValidationError("CryptoMaterials",
$"Crypto material {crypto.ComponentId} digest mismatch: expected {crypto.Digest}, got {result.ActualDigest}"));
}
}
}
// Check bundle expiration
if (manifest.ExpiresAt.HasValue && manifest.ExpiresAt.Value < now)
{
warnings.Add(new BundleValidationWarning("ExpiresAt", "Bundle has expired"));
}
// Check feed staleness using configurable threshold
foreach (var feed in manifest.Feeds)
{
var age = DateTimeOffset.UtcNow - feed.SnapshotAt;
if (age.TotalDays > 7)
var age = now - feed.SnapshotAt;
if (age.TotalDays > _options.MaxFeedAgeDays)
{
warnings.Add(new BundleValidationWarning("Feeds",
$"Feed {feed.FeedId} is {age.TotalDays:F0} days old"));
var message = $"Feed {feed.FeedId} is {age.TotalDays:F0} days old (threshold: {_options.MaxFeedAgeDays} days)";
if (_options.FailOnStaleFeed)
{
errors.Add(new BundleValidationError("Feeds", message));
}
else
{
warnings.Add(new BundleValidationWarning("Feeds", message));
}
}
}
// Verify bundle digest if present
if (manifest.BundleDigest is not null)
{
var computed = ComputeBundleDigest(manifest);