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

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StellaOps.Concelier.Documents;
@@ -32,7 +33,11 @@ internal sealed record UbuntuCursor(
lastPublished = value.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(
value.AsString,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
out var parsed) => parsed.ToUniversalTime(),
_ => null
};
}
@@ -47,10 +52,14 @@ internal sealed record UbuntuCursor(
public DocumentObject ToDocumentObject()
{
// Sort collections for deterministic serialization
var sortedPendingDocs = PendingDocuments.OrderBy(id => id).Select(id => id.ToString());
var sortedPendingMaps = PendingMappings.OrderBy(id => id).Select(id => id.ToString());
var doc = new DocumentObject
{
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString()))
["pendingDocuments"] = new DocumentArray(sortedPendingDocs),
["pendingMappings"] = new DocumentArray(sortedPendingMaps)
};
if (LastPublished.HasValue)
@@ -60,13 +69,16 @@ internal sealed record UbuntuCursor(
if (ProcessedNoticeIds.Count > 0)
{
doc["processedIds"] = new DocumentArray(ProcessedNoticeIds);
// Sort processed IDs for deterministic output
var sortedProcessedIds = ProcessedNoticeIds.OrderBy(id => id, StringComparer.Ordinal);
doc["processedIds"] = new DocumentArray(sortedProcessedIds);
}
if (FetchCache.Count > 0)
{
var cacheDoc = new DocumentObject();
foreach (var (key, entry) in FetchCache)
// Sort fetch cache keys for deterministic output
foreach (var (key, entry) in FetchCache.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
{
cacheDoc[key] = entry.ToDocumentObject();
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using StellaOps.Concelier.Documents;
using StorageContracts = StellaOps.Concelier.Storage.Contracts;
@@ -31,7 +32,11 @@ internal sealed record UbuntuFetchCacheEntry(string? ETag, DateTimeOffset? LastM
lastModified = modifiedValue.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(modifiedValue.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(modifiedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(
modifiedValue.AsString,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
out var parsed) => parsed.ToUniversalTime(),
_ => null
};
}

View File

@@ -32,7 +32,8 @@ internal static class UbuntuNoticeParser
continue;
}
var published = ParseDate(noticeElement, "published") ?? DateTimeOffset.UtcNow;
// Use MinValue instead of UtcNow for deterministic fallback on invalid/missing dates
var published = ParseDate(noticeElement, "published") ?? DateTimeOffset.MinValue;
var title = noticeElement.TryGetProperty("title", out var titleElement)
? titleElement.GetString() ?? noticeId
: noticeId;

View File

@@ -5,6 +5,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>

View File

@@ -150,7 +150,8 @@ public sealed class UbuntuConnector : IFeedConnector
var dtoDocument = ToDocument(notice);
var sha256 = ComputeNoticeHash(dtoDocument);
var documentId = existing?.Id ?? Guid.NewGuid();
// Use existing ID or derive deterministic ID from source + uri hash
var documentId = existing?.Id ?? ComputeDeterministicId(SourceName, detailUri.AbsoluteUri);
var record = new DocumentRecord(
documentId,
SourceName,
@@ -167,7 +168,9 @@ public sealed class UbuntuConnector : IFeedConnector
await _documentStore.UpsertAsync(record, cancellationToken).ConfigureAwait(false);
var dtoRecord = new DtoRecord(Guid.NewGuid(), record.Id, SourceName, "ubuntu.notice.v1", dtoDocument, now);
// Derive deterministic DTO ID from document ID + schema
var dtoId = ComputeDeterministicId(record.Id.ToString(), "ubuntu.notice.v1");
var dtoRecord = new DtoRecord(dtoId, record.Id, SourceName, "ubuntu.notice.v1", dtoDocument, now);
await _dtoStore.UpsertAsync(dtoRecord, cancellationToken).ConfigureAwait(false);
pendingMappings.Add(record.Id);
@@ -435,6 +438,15 @@ public sealed class UbuntuConnector : IFeedConnector
return Convert.ToHexString(hash).ToLowerInvariant();
}
private Guid ComputeDeterministicId(string source, string identifier)
{
// Deterministic GUID based on SHA-256 hash of source + identifier
var input = Encoding.UTF8.GetBytes($"{source}:{identifier}");
var hash = _hash.ComputeHash(input, HashAlgorithms.Sha256);
// Use first 16 bytes of hash as GUID
return new Guid(hash.AsSpan(0, 16));
}
private static DocumentObject ToDocument(UbuntuNoticeDto notice)
{
var packages = new DocumentArray();
@@ -486,14 +498,19 @@ public sealed class UbuntuConnector : IFeedConnector
private static UbuntuNoticeDto FromDocument(DocumentObject document)
{
var noticeId = document.GetValue("noticeId", string.Empty).AsString;
// Use MinValue instead of UtcNow for deterministic fallback on invalid/missing dates
var published = document.TryGetValue("published", out var publishedValue)
? publishedValue.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(publishedValue.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(publishedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => DateTimeOffset.UtcNow
DocumentType.String when DateTimeOffset.TryParse(
publishedValue.AsString,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
out var parsed) => parsed.ToUniversalTime(),
_ => DateTimeOffset.MinValue
}
: DateTimeOffset.UtcNow;
: DateTimeOffset.MinValue;
var title = document.GetValue("title", noticeId).AsString;
var summary = document.GetValue("summary", string.Empty).AsString;