feat(scanner): Complete PoE implementation with Windows compatibility fix
- Fix namespace conflicts (Subgraph → PoESubgraph) - Add hash sanitization for Windows filesystem (colon → underscore) - Update all test mocks to use It.IsAny<>() - Add direct orchestrator unit tests - All 8 PoE tests now passing (100% success rate) - Complete SPRINT_3500_0001_0001 documentation Fixes compilation errors and Windows filesystem compatibility issues. Tests: 8/8 passing Files: 8 modified, 1 new test, 1 completion report 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
namespace StellaOps.Concelier.ProofService.Postgres;
|
||||
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Concelier.ProofService;
|
||||
using StellaOps.Feedser.BinaryAnalysis.Models;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL implementation of patch repository.
|
||||
/// Queries vuln.patch_evidence and feedser.binary_fingerprints tables.
|
||||
/// </summary>
|
||||
public sealed class PostgresPatchRepository : IPatchRepository
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly ILogger<PostgresPatchRepository> _logger;
|
||||
|
||||
public PostgresPatchRepository(
|
||||
string connectionString,
|
||||
ILogger<PostgresPatchRepository> logger)
|
||||
{
|
||||
_connectionString = connectionString;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find patch headers mentioning the given CVE ID.
|
||||
/// Returns all matching patch headers ordered by parsed date (newest first).
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<PatchHeaderDto>> FindPatchHeadersByCveAsync(
|
||||
string cveId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
const string sql = @"
|
||||
SELECT
|
||||
patch_file_path AS PatchFilePath,
|
||||
origin AS Origin,
|
||||
parsed_at AS ParsedAt,
|
||||
cve_ids AS CveIds
|
||||
FROM vuln.patch_evidence
|
||||
WHERE @CveId = ANY(cve_ids)
|
||||
ORDER BY parsed_at DESC;
|
||||
";
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(_connectionString);
|
||||
await connection.OpenAsync(ct);
|
||||
|
||||
var results = await connection.QueryAsync<PatchHeaderDto>(
|
||||
new CommandDefinition(sql, new { CveId = cveId }, cancellationToken: ct));
|
||||
|
||||
var patchList = results.ToList();
|
||||
|
||||
_logger.LogDebug(
|
||||
"Found {Count} patch headers for {CveId}",
|
||||
patchList.Count, cveId);
|
||||
|
||||
return patchList;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Failed to query patch headers for {CveId}",
|
||||
cveId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find patch signatures (HunkSig matches) for the given CVE ID.
|
||||
/// Returns all matching signatures ordered by extraction date (newest first).
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<PatchSigDto>> FindPatchSignaturesByCveAsync(
|
||||
string cveId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
const string sql = @"
|
||||
SELECT
|
||||
commit_sha AS CommitSha,
|
||||
upstream_repo AS UpstreamRepo,
|
||||
extracted_at AS ExtractedAt,
|
||||
hunk_hash AS HunkHash
|
||||
FROM vuln.patch_signatures
|
||||
WHERE cve_id = @CveId
|
||||
ORDER BY extracted_at DESC;
|
||||
";
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(_connectionString);
|
||||
await connection.OpenAsync(ct);
|
||||
|
||||
var results = await connection.QueryAsync<PatchSigDto>(
|
||||
new CommandDefinition(sql, new { CveId = cveId }, cancellationToken: ct));
|
||||
|
||||
var sigList = results.ToList();
|
||||
|
||||
_logger.LogDebug(
|
||||
"Found {Count} patch signatures for {CveId}",
|
||||
sigList.Count, cveId);
|
||||
|
||||
return sigList;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Failed to query patch signatures for {CveId}",
|
||||
cveId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find binary fingerprints for the given CVE ID.
|
||||
/// Returns all matching fingerprints ordered by extraction date (newest first).
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<BinaryFingerprint>> FindBinaryFingerprintsByCveAsync(
|
||||
string cveId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
const string sql = @"
|
||||
SELECT
|
||||
fingerprint_id AS FingerprintId,
|
||||
cve_id AS CveId,
|
||||
method AS Method,
|
||||
fingerprint_value AS FingerprintValue,
|
||||
target_binary AS TargetBinary,
|
||||
target_function AS TargetFunction,
|
||||
architecture AS Architecture,
|
||||
format AS Format,
|
||||
compiler AS Compiler,
|
||||
optimization_level AS OptimizationLevel,
|
||||
has_debug_symbols AS HasDebugSymbols,
|
||||
file_offset AS FileOffset,
|
||||
region_size AS RegionSize,
|
||||
extracted_at AS ExtractedAt,
|
||||
extractor_version AS ExtractorVersion
|
||||
FROM feedser.binary_fingerprints
|
||||
WHERE cve_id = @CveId
|
||||
ORDER BY extracted_at DESC;
|
||||
";
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(_connectionString);
|
||||
await connection.OpenAsync(ct);
|
||||
|
||||
var results = await connection.QueryAsync<BinaryFingerprintRow>(
|
||||
new CommandDefinition(sql, new { CveId = cveId }, cancellationToken: ct));
|
||||
|
||||
var fingerprints = results.Select(row => new BinaryFingerprint
|
||||
{
|
||||
FingerprintId = row.FingerprintId,
|
||||
CveId = row.CveId,
|
||||
Method = Enum.Parse<FingerprintMethod>(row.Method, ignoreCase: true),
|
||||
FingerprintValue = row.FingerprintValue,
|
||||
TargetBinary = row.TargetBinary,
|
||||
TargetFunction = row.TargetFunction,
|
||||
Metadata = new FingerprintMetadata
|
||||
{
|
||||
Architecture = row.Architecture,
|
||||
Format = row.Format,
|
||||
Compiler = row.Compiler,
|
||||
OptimizationLevel = row.OptimizationLevel,
|
||||
HasDebugSymbols = row.HasDebugSymbols,
|
||||
FileOffset = row.FileOffset,
|
||||
RegionSize = row.RegionSize
|
||||
},
|
||||
ExtractedAt = row.ExtractedAt,
|
||||
ExtractorVersion = row.ExtractorVersion
|
||||
}).ToList();
|
||||
|
||||
_logger.LogDebug(
|
||||
"Found {Count} binary fingerprints for {CveId}",
|
||||
fingerprints.Count, cveId);
|
||||
|
||||
return fingerprints;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Failed to query binary fingerprints for {CveId}",
|
||||
cveId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal row mapping class for Dapper
|
||||
private sealed class BinaryFingerprintRow
|
||||
{
|
||||
public required string FingerprintId { get; init; }
|
||||
public required string CveId { get; init; }
|
||||
public required string Method { get; init; }
|
||||
public required string FingerprintValue { get; init; }
|
||||
public required string TargetBinary { get; init; }
|
||||
public string? TargetFunction { get; init; }
|
||||
public required string Architecture { get; init; }
|
||||
public required string Format { get; init; }
|
||||
public string? Compiler { get; init; }
|
||||
public string? OptimizationLevel { get; init; }
|
||||
public required bool HasDebugSymbols { get; init; }
|
||||
public long? FileOffset { get; init; }
|
||||
public long? RegionSize { get; init; }
|
||||
public required DateTimeOffset ExtractedAt { get; init; }
|
||||
public required string ExtractorVersion { get; init; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user