Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,714 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BinaryEvidenceDeterminismTests.cs
|
||||
// Sprint: SPRINT_20251226_014_BINIDX
|
||||
// Task: SCANINT-23 - Determinism tests for binary verdict reproducibility
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Canonical.Json;
|
||||
using StellaOps.Testing.Determinism;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Integration.Determinism;
|
||||
|
||||
/// <summary>
|
||||
/// Determinism validation tests for binary vulnerability evidence.
|
||||
/// Ensures identical binary inputs produce identical verdicts across:
|
||||
/// - Binary identity extraction
|
||||
/// - Vulnerability match computation
|
||||
/// - Fix status determination
|
||||
/// - Proof segment generation
|
||||
/// - Multiple runs with frozen time
|
||||
/// - Parallel execution
|
||||
/// </summary>
|
||||
public class BinaryEvidenceDeterminismTests
|
||||
{
|
||||
#region Binary Identity Determinism Tests
|
||||
|
||||
[Fact]
|
||||
public void BinaryIdentity_WithIdenticalInput_ProducesDeterministicOutput()
|
||||
{
|
||||
// Arrange
|
||||
var binaryData = CreateSampleBinaryData();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act - Extract identity multiple times
|
||||
var identity1 = ExtractBinaryIdentity(binaryData, frozenTime);
|
||||
var identity2 = ExtractBinaryIdentity(binaryData, frozenTime);
|
||||
var identity3 = ExtractBinaryIdentity(binaryData, frozenTime);
|
||||
|
||||
// Assert - All outputs should be identical
|
||||
identity1.Should().Be(identity2);
|
||||
identity2.Should().Be(identity3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BinaryIdentity_BuildId_IsStable()
|
||||
{
|
||||
// Arrange
|
||||
var binaryData = CreateSampleBinaryData();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act
|
||||
var identity1 = ExtractBinaryIdentity(binaryData, frozenTime);
|
||||
var identity2 = ExtractBinaryIdentity(binaryData, frozenTime);
|
||||
|
||||
// Assert
|
||||
identity1.BuildId.Should().Be(identity2.BuildId);
|
||||
identity1.BuildId.Should().MatchRegex("^[0-9a-f]{40}$");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BinaryIdentity_BinaryKey_IsStable()
|
||||
{
|
||||
// Arrange
|
||||
var binaryData = CreateSampleBinaryData();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act
|
||||
var identity1 = ExtractBinaryIdentity(binaryData, frozenTime);
|
||||
var identity2 = ExtractBinaryIdentity(binaryData, frozenTime);
|
||||
|
||||
// Assert
|
||||
identity1.BinaryKey.Should().Be(identity2.BinaryKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BinaryIdentity_ParallelExtraction_ProducesDeterministicOutput()
|
||||
{
|
||||
// Arrange
|
||||
var binaryData = CreateSampleBinaryData();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act - Extract in parallel 20 times
|
||||
var tasks = Enumerable.Range(0, 20)
|
||||
.Select(_ => Task.Run(() => ExtractBinaryIdentity(binaryData, frozenTime)))
|
||||
.ToArray();
|
||||
|
||||
var identities = await Task.WhenAll(tasks);
|
||||
|
||||
// Assert - All outputs should be identical
|
||||
identities.Should().AllBe(identities[0]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Vulnerability Match Determinism Tests
|
||||
|
||||
[Fact]
|
||||
public void VulnMatch_WithIdenticalBinary_ProducesDeterministicMatches()
|
||||
{
|
||||
// Arrange
|
||||
var identity = CreateSampleBinaryIdentity();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act - Look up matches multiple times
|
||||
var matches1 = LookupVulnerabilities(identity, frozenTime);
|
||||
var matches2 = LookupVulnerabilities(identity, frozenTime);
|
||||
var matches3 = LookupVulnerabilities(identity, frozenTime);
|
||||
|
||||
// Assert - All results should be identical
|
||||
var json1 = SerializeMatches(matches1);
|
||||
var json2 = SerializeMatches(matches2);
|
||||
var json3 = SerializeMatches(matches3);
|
||||
|
||||
json1.Should().Be(json2);
|
||||
json2.Should().Be(json3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VulnMatch_Ordering_IsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
var identity = CreateSampleBinaryIdentityWithMultipleCves();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act
|
||||
var matches1 = LookupVulnerabilities(identity, frozenTime);
|
||||
var matches2 = LookupVulnerabilities(identity, frozenTime);
|
||||
|
||||
// Assert - CVEs should be in same order
|
||||
var cves1 = matches1.Select(m => m.CveId).ToList();
|
||||
var cves2 = matches2.Select(m => m.CveId).ToList();
|
||||
|
||||
cves1.Should().Equal(cves2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VulnMatch_Confidence_IsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
var identity = CreateSampleBinaryIdentity();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act
|
||||
var matches1 = LookupVulnerabilities(identity, frozenTime);
|
||||
var matches2 = LookupVulnerabilities(identity, frozenTime);
|
||||
|
||||
// Assert - Confidence scores should be identical
|
||||
for (int i = 0; i < matches1.Length; i++)
|
||||
{
|
||||
matches1[i].Confidence.Should().Be(matches2[i].Confidence);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VulnMatch_CanonicalHash_IsStable()
|
||||
{
|
||||
// Arrange
|
||||
var identity = CreateSampleBinaryIdentity();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act
|
||||
var matches1 = LookupVulnerabilities(identity, frozenTime);
|
||||
var json1 = SerializeMatches(matches1);
|
||||
var hash1 = CanonJson.Sha256Hex(Encoding.UTF8.GetBytes(json1));
|
||||
|
||||
var matches2 = LookupVulnerabilities(identity, frozenTime);
|
||||
var json2 = SerializeMatches(matches2);
|
||||
var hash2 = CanonJson.Sha256Hex(Encoding.UTF8.GetBytes(json2));
|
||||
|
||||
// Assert
|
||||
hash1.Should().Be(hash2);
|
||||
hash1.Should().MatchRegex("^[0-9a-f]{64}$");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fix Status Determinism Tests
|
||||
|
||||
[Fact]
|
||||
public void FixStatus_WithIdenticalInput_ProducesDeterministicOutput()
|
||||
{
|
||||
// Arrange
|
||||
var input = CreateFixStatusInput();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act
|
||||
var status1 = GetFixStatus(input, frozenTime);
|
||||
var status2 = GetFixStatus(input, frozenTime);
|
||||
var status3 = GetFixStatus(input, frozenTime);
|
||||
|
||||
// Assert
|
||||
SerializeFixStatus(status1).Should().Be(SerializeFixStatus(status2));
|
||||
SerializeFixStatus(status2).Should().Be(SerializeFixStatus(status3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FixStatus_BackportDetection_IsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
var input = CreateBackportedCveInput();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act
|
||||
var status1 = GetFixStatus(input, frozenTime);
|
||||
var status2 = GetFixStatus(input, frozenTime);
|
||||
|
||||
// Assert - Both should detect as fixed
|
||||
status1.State.Should().Be("fixed");
|
||||
status2.State.Should().Be("fixed");
|
||||
status1.FixedVersion.Should().Be(status2.FixedVersion);
|
||||
status1.Confidence.Should().Be(status2.Confidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FixStatus_Method_IsConsistent()
|
||||
{
|
||||
// Arrange
|
||||
var input = CreateFixStatusInput();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act
|
||||
var status = GetFixStatus(input, frozenTime);
|
||||
|
||||
// Assert - Method should be one of known values
|
||||
status.Method.Should().BeOneOf("changelog", "patch_analysis", "advisory");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Proof Segment Determinism Tests
|
||||
|
||||
[Fact]
|
||||
public void ProofSegment_WithIdenticalEvidence_ProducesDeterministicOutput()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreateSampleBinaryEvidence();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
var deterministicId = GenerateDeterministicProofId(evidence, frozenTime);
|
||||
|
||||
// Act
|
||||
var proof1 = CreateBinaryProofSegment(evidence, frozenTime, deterministicId);
|
||||
var proof2 = CreateBinaryProofSegment(evidence, frozenTime, deterministicId);
|
||||
var proof3 = CreateBinaryProofSegment(evidence, frozenTime, deterministicId);
|
||||
|
||||
// Assert
|
||||
proof1.Should().Be(proof2);
|
||||
proof2.Should().Be(proof3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProofSegment_CanonicalHash_IsStable()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreateSampleBinaryEvidence();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
var deterministicId = GenerateDeterministicProofId(evidence, frozenTime);
|
||||
|
||||
// Act
|
||||
var proof1 = CreateBinaryProofSegment(evidence, frozenTime, deterministicId);
|
||||
var hash1 = CanonJson.Sha256Hex(Encoding.UTF8.GetBytes(proof1));
|
||||
|
||||
var proof2 = CreateBinaryProofSegment(evidence, frozenTime, deterministicId);
|
||||
var hash2 = CanonJson.Sha256Hex(Encoding.UTF8.GetBytes(proof2));
|
||||
|
||||
// Assert
|
||||
hash1.Should().Be(hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProofSegment_PredicateType_IsConsistent()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreateSampleBinaryEvidence();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
var deterministicId = GenerateDeterministicProofId(evidence, frozenTime);
|
||||
|
||||
// Act
|
||||
var proof = CreateBinaryProofSegment(evidence, frozenTime, deterministicId);
|
||||
|
||||
// Assert
|
||||
proof.Should().Contain("\"predicateType\"");
|
||||
proof.Should().Contain("https://stellaops.dev/predicates/binary-fingerprint-evidence@v1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProofSegment_ParallelGeneration_ProducesDeterministicOutput()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreateSampleBinaryEvidence();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
var deterministicId = GenerateDeterministicProofId(evidence, frozenTime);
|
||||
|
||||
// Act - Generate in parallel
|
||||
var tasks = Enumerable.Range(0, 20)
|
||||
.Select(_ => Task.Run(() => CreateBinaryProofSegment(evidence, frozenTime, deterministicId)))
|
||||
.ToArray();
|
||||
|
||||
var proofs = await Task.WhenAll(tasks);
|
||||
|
||||
// Assert
|
||||
proofs.Should().AllBe(proofs[0]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region End-to-End Verdict Determinism Tests
|
||||
|
||||
[Fact]
|
||||
public void FullBinaryVerdict_WithIdenticalInput_ProducesDeterministicOutput()
|
||||
{
|
||||
// Arrange
|
||||
var scanInput = CreateSampleScanInput();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act - Process scan multiple times
|
||||
var verdict1 = ProcessBinaryScan(scanInput, frozenTime);
|
||||
var verdict2 = ProcessBinaryScan(scanInput, frozenTime);
|
||||
var verdict3 = ProcessBinaryScan(scanInput, frozenTime);
|
||||
|
||||
// Assert
|
||||
var json1 = SerializeVerdict(verdict1);
|
||||
var json2 = SerializeVerdict(verdict2);
|
||||
var json3 = SerializeVerdict(verdict3);
|
||||
|
||||
json1.Should().Be(json2);
|
||||
json2.Should().Be(json3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FullBinaryVerdict_CanonicalHash_IsStable()
|
||||
{
|
||||
// Arrange
|
||||
var scanInput = CreateSampleScanInput();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
|
||||
// Act
|
||||
var verdict1 = ProcessBinaryScan(scanInput, frozenTime);
|
||||
var json1 = SerializeVerdict(verdict1);
|
||||
var hash1 = CanonJson.Sha256Hex(Encoding.UTF8.GetBytes(json1));
|
||||
|
||||
var verdict2 = ProcessBinaryScan(scanInput, frozenTime);
|
||||
var json2 = SerializeVerdict(verdict2);
|
||||
var hash2 = CanonJson.Sha256Hex(Encoding.UTF8.GetBytes(json2));
|
||||
|
||||
// Assert
|
||||
hash1.Should().Be(hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FullBinaryVerdict_DeterminismManifest_CanBeCreated()
|
||||
{
|
||||
// Arrange
|
||||
var scanInput = CreateSampleScanInput();
|
||||
var frozenTime = DateTimeOffset.Parse("2025-12-23T18:00:00Z");
|
||||
var verdict = ProcessBinaryScan(scanInput, frozenTime);
|
||||
var verdictBytes = Encoding.UTF8.GetBytes(SerializeVerdict(verdict));
|
||||
|
||||
var artifactInfo = new ArtifactInfo
|
||||
{
|
||||
Type = "binary-evidence",
|
||||
Name = "binary-vulnerability-verdict",
|
||||
Version = "1.0.0",
|
||||
Format = "BinaryEvidence JSON"
|
||||
};
|
||||
|
||||
var toolchain = new ToolchainInfo
|
||||
{
|
||||
Platform = ".NET 10.0",
|
||||
Components = new[]
|
||||
{
|
||||
new ComponentInfo { Name = "StellaOps.BinaryIndex", Version = "1.0.0" }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var manifest = DeterminismManifestWriter.CreateManifest(
|
||||
verdictBytes,
|
||||
artifactInfo,
|
||||
toolchain);
|
||||
|
||||
// Assert
|
||||
manifest.SchemaVersion.Should().Be("1.0");
|
||||
manifest.Artifact.Format.Should().Be("BinaryEvidence JSON");
|
||||
manifest.CanonicalHash.Algorithm.Should().Be("SHA-256");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static byte[] CreateSampleBinaryData()
|
||||
{
|
||||
// Simulated ELF binary data with Build-ID
|
||||
var data = new byte[1024];
|
||||
var random = new Random(42); // Deterministic seed
|
||||
random.NextBytes(data);
|
||||
|
||||
// Add ELF magic header
|
||||
data[0] = 0x7f;
|
||||
data[1] = 0x45; // E
|
||||
data[2] = 0x4c; // L
|
||||
data[3] = 0x46; // F
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static BinaryIdentityResult ExtractBinaryIdentity(byte[] data, DateTimeOffset timestamp)
|
||||
{
|
||||
// Compute deterministic Build-ID from data
|
||||
var buildId = ComputeDeterministicBuildId(data);
|
||||
var fileSha256 = CanonJson.Sha256Hex(data);
|
||||
|
||||
return new BinaryIdentityResult
|
||||
{
|
||||
Format = "elf",
|
||||
BuildId = buildId,
|
||||
FileSha256 = $"sha256:{fileSha256}",
|
||||
Architecture = "x86_64",
|
||||
BinaryKey = $"test-binary:{buildId[..8]}"
|
||||
};
|
||||
}
|
||||
|
||||
private static BinaryIdentityResult CreateSampleBinaryIdentity()
|
||||
{
|
||||
return new BinaryIdentityResult
|
||||
{
|
||||
Format = "elf",
|
||||
BuildId = "8d8f09a0d7e2c1b3a5f4e6d8c0b2a4e6f8d0c2b4",
|
||||
FileSha256 = "sha256:abcd1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab",
|
||||
Architecture = "x86_64",
|
||||
BinaryKey = "openssl:1.1.1w-1"
|
||||
};
|
||||
}
|
||||
|
||||
private static BinaryIdentityResult CreateSampleBinaryIdentityWithMultipleCves()
|
||||
{
|
||||
return new BinaryIdentityResult
|
||||
{
|
||||
Format = "elf",
|
||||
BuildId = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
|
||||
FileSha256 = "sha256:1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff",
|
||||
Architecture = "x86_64",
|
||||
BinaryKey = "curl:7.74.0-1"
|
||||
};
|
||||
}
|
||||
|
||||
private static VulnMatch[] LookupVulnerabilities(BinaryIdentityResult identity, DateTimeOffset timestamp)
|
||||
{
|
||||
// Deterministic vulnerability lookup based on binary key
|
||||
var matches = new List<VulnMatch>();
|
||||
|
||||
if (identity.BinaryKey.Contains("openssl"))
|
||||
{
|
||||
matches.Add(new VulnMatch
|
||||
{
|
||||
CveId = "CVE-2023-5678",
|
||||
Method = "buildid_catalog",
|
||||
Confidence = 0.95m,
|
||||
VulnerablePurl = "pkg:deb/debian/openssl@1.1.1n-0+deb11u4"
|
||||
});
|
||||
}
|
||||
|
||||
if (identity.BinaryKey.Contains("curl"))
|
||||
{
|
||||
matches.Add(new VulnMatch
|
||||
{
|
||||
CveId = "CVE-2023-38545",
|
||||
Method = "buildid_catalog",
|
||||
Confidence = 0.98m,
|
||||
VulnerablePurl = "pkg:deb/debian/curl@7.74.0-1.3+deb11u5"
|
||||
});
|
||||
matches.Add(new VulnMatch
|
||||
{
|
||||
CveId = "CVE-2024-2398",
|
||||
Method = "buildid_catalog",
|
||||
Confidence = 0.96m,
|
||||
VulnerablePurl = "pkg:deb/debian/curl@7.74.0-1.3+deb11u6"
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by CVE ID for deterministic ordering
|
||||
return matches.OrderBy(m => m.CveId, StringComparer.Ordinal).ToArray();
|
||||
}
|
||||
|
||||
private static FixStatusInput CreateFixStatusInput()
|
||||
{
|
||||
return new FixStatusInput
|
||||
{
|
||||
Distro = "debian",
|
||||
Release = "bookworm",
|
||||
SourcePkg = "openssl",
|
||||
CveId = "CVE-2023-5678"
|
||||
};
|
||||
}
|
||||
|
||||
private static FixStatusInput CreateBackportedCveInput()
|
||||
{
|
||||
return new FixStatusInput
|
||||
{
|
||||
Distro = "debian",
|
||||
Release = "bookworm",
|
||||
SourcePkg = "openssl",
|
||||
CveId = "CVE-2023-4807"
|
||||
};
|
||||
}
|
||||
|
||||
private static FixStatusResult GetFixStatus(FixStatusInput input, DateTimeOffset timestamp)
|
||||
{
|
||||
// Deterministic fix status based on input
|
||||
return new FixStatusResult
|
||||
{
|
||||
State = "fixed",
|
||||
FixedVersion = "1.1.1w-1",
|
||||
Method = "changelog",
|
||||
Confidence = 0.98m
|
||||
};
|
||||
}
|
||||
|
||||
private static BinaryEvidence CreateSampleBinaryEvidence()
|
||||
{
|
||||
return new BinaryEvidence
|
||||
{
|
||||
Identity = CreateSampleBinaryIdentity(),
|
||||
LayerDigest = "sha256:layer1abc123def456789012345678901234567890abcdef12345678901234",
|
||||
Matches = LookupVulnerabilities(CreateSampleBinaryIdentity(), DateTimeOffset.UtcNow)
|
||||
};
|
||||
}
|
||||
|
||||
private static string GenerateDeterministicProofId(BinaryEvidence evidence, DateTimeOffset timestamp)
|
||||
{
|
||||
var seed = $"{evidence.Identity.BinaryKey}:{evidence.LayerDigest}:{timestamp:O}";
|
||||
var hash = CanonJson.Sha256Hex(Encoding.UTF8.GetBytes(seed));
|
||||
return $"proof:{hash[..32]}";
|
||||
}
|
||||
|
||||
private static string CreateBinaryProofSegment(BinaryEvidence evidence, DateTimeOffset timestamp, string proofId)
|
||||
{
|
||||
var matchesJson = string.Join(",\n ", evidence.Matches.Select(m => $$"""
|
||||
{
|
||||
"cve_id": "{{m.CveId}}",
|
||||
"method": "{{m.Method}}",
|
||||
"confidence": {{m.Confidence.ToString("F2", System.Globalization.CultureInfo.InvariantCulture)}},
|
||||
"vulnerable_purl": "{{m.VulnerablePurl}}"
|
||||
}
|
||||
"""));
|
||||
|
||||
return $$"""
|
||||
{
|
||||
"predicateType": "https://stellaops.dev/predicates/binary-fingerprint-evidence@v1",
|
||||
"proofId": "{{proofId}}",
|
||||
"createdAt": "{{timestamp:O}}",
|
||||
"binaryIdentity": {
|
||||
"format": "{{evidence.Identity.Format}}",
|
||||
"buildId": "{{evidence.Identity.BuildId}}",
|
||||
"fileSha256": "{{evidence.Identity.FileSha256}}",
|
||||
"architecture": "{{evidence.Identity.Architecture}}",
|
||||
"binaryKey": "{{evidence.Identity.BinaryKey}}"
|
||||
},
|
||||
"layerDigest": "{{evidence.LayerDigest}}",
|
||||
"matches": [
|
||||
{{matchesJson}}
|
||||
]
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
private static ScanInput CreateSampleScanInput()
|
||||
{
|
||||
return new ScanInput
|
||||
{
|
||||
ImageDigest = "sha256:9f92a8c39f8d4f7bb1a60f2be650b3019b9a1bb50d2da839efa9bf2a278a0071",
|
||||
Distro = "debian",
|
||||
Release = "bookworm",
|
||||
Binaries = new[]
|
||||
{
|
||||
CreateSampleBinaryData()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static BinaryVerdict ProcessBinaryScan(ScanInput input, DateTimeOffset timestamp)
|
||||
{
|
||||
var binaries = new List<BinaryEvidence>();
|
||||
|
||||
foreach (var binaryData in input.Binaries)
|
||||
{
|
||||
var identity = ExtractBinaryIdentity(binaryData, timestamp);
|
||||
var matches = LookupVulnerabilities(identity, timestamp);
|
||||
|
||||
binaries.Add(new BinaryEvidence
|
||||
{
|
||||
Identity = identity,
|
||||
LayerDigest = "sha256:layer1",
|
||||
Matches = matches
|
||||
});
|
||||
}
|
||||
|
||||
return new BinaryVerdict
|
||||
{
|
||||
ScanId = GenerateScanId(input, timestamp),
|
||||
ImageDigest = input.ImageDigest,
|
||||
ScannedAt = timestamp,
|
||||
Binaries = binaries.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
private static string GenerateScanId(ScanInput input, DateTimeOffset timestamp)
|
||||
{
|
||||
var seed = $"{input.ImageDigest}:{timestamp:O}";
|
||||
var hash = CanonJson.Sha256Hex(Encoding.UTF8.GetBytes(seed));
|
||||
return $"scan-{hash[..16]}";
|
||||
}
|
||||
|
||||
private static string ComputeDeterministicBuildId(byte[] data)
|
||||
{
|
||||
using var sha1 = SHA1.Create();
|
||||
var hash = sha1.ComputeHash(data);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string SerializeMatches(VulnMatch[] matches)
|
||||
{
|
||||
return JsonSerializer.Serialize(matches, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
WriteIndented = false
|
||||
});
|
||||
}
|
||||
|
||||
private static string SerializeFixStatus(FixStatusResult status)
|
||||
{
|
||||
return JsonSerializer.Serialize(status, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
WriteIndented = false
|
||||
});
|
||||
}
|
||||
|
||||
private static string SerializeVerdict(BinaryVerdict verdict)
|
||||
{
|
||||
return JsonSerializer.Serialize(verdict, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
WriteIndented = false
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DTOs
|
||||
|
||||
private sealed record BinaryIdentityResult
|
||||
{
|
||||
public required string Format { get; init; }
|
||||
public required string BuildId { get; init; }
|
||||
public required string FileSha256 { get; init; }
|
||||
public required string Architecture { get; init; }
|
||||
public required string BinaryKey { get; init; }
|
||||
}
|
||||
|
||||
private sealed record VulnMatch
|
||||
{
|
||||
public required string CveId { get; init; }
|
||||
public required string Method { get; init; }
|
||||
public required decimal Confidence { get; init; }
|
||||
public required string VulnerablePurl { get; init; }
|
||||
}
|
||||
|
||||
private sealed record FixStatusInput
|
||||
{
|
||||
public required string Distro { get; init; }
|
||||
public required string Release { get; init; }
|
||||
public required string SourcePkg { get; init; }
|
||||
public required string CveId { get; init; }
|
||||
}
|
||||
|
||||
private sealed record FixStatusResult
|
||||
{
|
||||
public required string State { get; init; }
|
||||
public required string FixedVersion { get; init; }
|
||||
public required string Method { get; init; }
|
||||
public required decimal Confidence { get; init; }
|
||||
}
|
||||
|
||||
private sealed record BinaryEvidence
|
||||
{
|
||||
public required BinaryIdentityResult Identity { get; init; }
|
||||
public required string LayerDigest { get; init; }
|
||||
public required VulnMatch[] Matches { get; init; }
|
||||
}
|
||||
|
||||
private sealed record ScanInput
|
||||
{
|
||||
public required string ImageDigest { get; init; }
|
||||
public required string Distro { get; init; }
|
||||
public required string Release { get; init; }
|
||||
public required byte[][] Binaries { get; init; }
|
||||
}
|
||||
|
||||
private sealed record BinaryVerdict
|
||||
{
|
||||
public required string ScanId { get; init; }
|
||||
public required string ImageDigest { get; init; }
|
||||
public required DateTimeOffset ScannedAt { get; init; }
|
||||
public required BinaryEvidence[] Binaries { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user