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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user