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

@@ -195,7 +195,15 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
{
try
{
var filePath = Path.Combine(bundleDir, entry.RelativePath.Replace('/', Path.DirectorySeparatorChar));
// Validate path to prevent traversal attacks
if (!PathValidation.IsSafeRelativePath(entry.RelativePath))
{
result.Failed++;
result.Errors.Add($"Unsafe path detected: {entry.RelativePath}");
continue;
}
var filePath = PathValidation.SafeCombine(bundleDir, entry.RelativePath);
if (!File.Exists(filePath))
{
result.Failed++;
@@ -250,7 +258,15 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
{
try
{
var filePath = Path.Combine(bundleDir, entry.RelativePath.Replace('/', Path.DirectorySeparatorChar));
// Validate path to prevent traversal attacks
if (!PathValidation.IsSafeRelativePath(entry.RelativePath))
{
result.Failed++;
result.Errors.Add($"Unsafe path detected: {entry.RelativePath}");
continue;
}
var filePath = PathValidation.SafeCombine(bundleDir, entry.RelativePath);
if (!File.Exists(filePath))
{
result.Failed++;
@@ -305,7 +321,15 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
{
try
{
var filePath = Path.Combine(bundleDir, entry.RelativePath.Replace('/', Path.DirectorySeparatorChar));
// Validate path to prevent traversal attacks
if (!PathValidation.IsSafeRelativePath(entry.RelativePath))
{
result.Failed++;
result.Errors.Add($"Unsafe path detected: {entry.RelativePath}");
continue;
}
var filePath = PathValidation.SafeCombine(bundleDir, entry.RelativePath);
if (!File.Exists(filePath))
{
result.Failed++;
@@ -349,9 +373,52 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
private static async Task ExtractBundleAsync(string bundlePath, string targetDir, CancellationToken ct)
{
var normalizedTargetDir = Path.GetFullPath(targetDir);
await using var fileStream = File.OpenRead(bundlePath);
await using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
await TarFile.ExtractToDirectoryAsync(gzipStream, targetDir, overwriteFiles: true, ct);
await using var tarReader = new TarReader(gzipStream, leaveOpen: false);
while (await tarReader.GetNextEntryAsync(copyData: true, ct) is { } entry)
{
if (string.IsNullOrEmpty(entry.Name))
{
continue;
}
// Validate entry path to prevent traversal attacks
if (!PathValidation.IsSafeRelativePath(entry.Name))
{
throw new InvalidOperationException($"Unsafe tar entry path detected: {entry.Name}");
}
var destinationPath = Path.GetFullPath(Path.Combine(normalizedTargetDir, entry.Name));
// Verify the path is within the target directory
if (!destinationPath.StartsWith(normalizedTargetDir, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"Tar entry path escapes target directory: {entry.Name}");
}
// Create directory if needed
var entryDir = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrEmpty(entryDir))
{
Directory.CreateDirectory(entryDir);
}
// Extract based on entry type
if (entry.EntryType == TarEntryType.Directory)
{
Directory.CreateDirectory(destinationPath);
}
else if (entry.EntryType == TarEntryType.RegularFile ||
entry.EntryType == TarEntryType.V7RegularFile)
{
await entry.ExtractToFileAsync(destinationPath, overwrite: true, ct);
}
// Skip symbolic links and other special entry types for security
}
}
private sealed class ModuleImportResult

View File

@@ -5,6 +5,7 @@
// Description: Signs snapshot manifests using DSSE format for integrity verification.
// -----------------------------------------------------------------------------
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -196,8 +197,9 @@ public sealed class SnapshotManifestSigner : ISnapshotManifestSigner
{
var typeBytes = Encoding.UTF8.GetBytes(payloadType);
var prefixBytes = Encoding.UTF8.GetBytes(PreAuthenticationEncodingPrefix);
var typeLenStr = typeBytes.Length.ToString();
var payloadLenStr = payload.Length.ToString();
// Use InvariantCulture to ensure ASCII decimal digits per DSSE spec
var typeLenStr = typeBytes.Length.ToString(CultureInfo.InvariantCulture);
var payloadLenStr = payload.Length.ToString(CultureInfo.InvariantCulture);
var totalLen = prefixBytes.Length + 1 +
typeLenStr.Length + 1 +

View File

@@ -178,39 +178,15 @@ public sealed class TimeAnchorService : ITimeAnchorService
CancellationToken cancellationToken)
{
// Roughtime is a cryptographic time synchronization protocol
// This is a placeholder implementation - full implementation would use a Roughtime client
// Full implementation requires a Roughtime client library
var serverUrl = request.Source?["roughtime:".Length..] ?? "roughtime.cloudflare.com:2003";
// For now, fallback to local with indication of intended source
var anchorTime = _timeProvider.GetUtcNow();
var anchorData = new RoughtimeAnchorData
{
Timestamp = anchorTime,
Server = serverUrl,
Midpoint = anchorTime.ToUnixTimeSeconds(),
Radius = 1000000, // 1 second radius in microseconds
Nonce = _guidProvider.NewGuid().ToString("N"),
MerkleRoot = request.MerkleRoot
};
var anchorJson = JsonSerializer.Serialize(anchorData, JsonOptions);
var anchorBytes = Encoding.UTF8.GetBytes(anchorJson);
var tokenDigest = $"sha256:{Convert.ToHexString(SHA256.HashData(anchorBytes)).ToLowerInvariant()}";
await Task.CompletedTask;
return new TimeAnchorResult
{
Success = true,
Content = new TimeAnchorContent
{
AnchorTime = anchorTime,
Source = $"roughtime:{serverUrl}",
TokenDigest = tokenDigest
},
TokenBytes = anchorBytes,
Warning = "Roughtime client not implemented; using simulated response"
};
// Per no-silent-stubs rule: unimplemented paths must fail explicitly
return TimeAnchorResult.Failed(
$"Roughtime time anchor source '{serverUrl}' is not implemented. " +
"Use 'local' source or implement Roughtime client integration.");
}
private async Task<TimeAnchorResult> CreateRfc3161AnchorAsync(
@@ -218,37 +194,15 @@ public sealed class TimeAnchorService : ITimeAnchorService
CancellationToken cancellationToken)
{
// RFC 3161 is the Internet X.509 PKI Time-Stamp Protocol (TSP)
// This is a placeholder implementation - full implementation would use a TSA client
// Full implementation requires a TSA client library
var tsaUrl = request.Source?["rfc3161:".Length..] ?? "http://timestamp.digicert.com";
var anchorTime = _timeProvider.GetUtcNow();
var anchorData = new Rfc3161AnchorData
{
Timestamp = anchorTime,
TsaUrl = tsaUrl,
SerialNumber = _guidProvider.NewGuid().ToString("N"),
PolicyOid = "2.16.840.1.114412.2.1", // DigiCert timestamp policy
MerkleRoot = request.MerkleRoot
};
var anchorJson = JsonSerializer.Serialize(anchorData, JsonOptions);
var anchorBytes = Encoding.UTF8.GetBytes(anchorJson);
var tokenDigest = $"sha256:{Convert.ToHexString(SHA256.HashData(anchorBytes)).ToLowerInvariant()}";
await Task.CompletedTask;
return new TimeAnchorResult
{
Success = true,
Content = new TimeAnchorContent
{
AnchorTime = anchorTime,
Source = $"rfc3161:{tsaUrl}",
TokenDigest = tokenDigest
},
TokenBytes = anchorBytes,
Warning = "RFC 3161 TSA client not implemented; using simulated response"
};
// Per no-silent-stubs rule: unimplemented paths must fail explicitly
return TimeAnchorResult.Failed(
$"RFC 3161 time anchor source '{tsaUrl}' is not implemented. " +
"Use 'local' source or implement RFC 3161 TSA client integration.");
}
private sealed record LocalAnchorData

View File

@@ -4,6 +4,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using StellaOps.AirGap.Sync.Services;
using StellaOps.AirGap.Sync.Stores;
using StellaOps.AirGap.Sync.Transport;
@@ -42,7 +43,8 @@ public static class AirGapSyncServiceCollectionExtensions
{
var timeProvider = sp.GetService<TimeProvider>() ?? TimeProvider.System;
var stateStore = sp.GetRequiredService<IHlcStateStore>();
return new HybridLogicalClock.HybridLogicalClock(timeProvider, nodeId, stateStore);
var logger = sp.GetRequiredService<ILogger<HybridLogicalClock.HybridLogicalClock>>();
return new HybridLogicalClock.HybridLogicalClock(timeProvider, nodeId, stateStore, logger);
});
// Register deterministic GUID provider