using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using StellaOps.Concelier.Connector.Common; using StellaOps.Concelier.Connector.Ghsa; using StellaOps.Concelier.Connector.Ghsa.Internal; using StellaOps.Concelier.Connector.Nvd; using StellaOps.Concelier.Connector.Osv; using StellaOps.Concelier.Connector.Osv.Internal; using StellaOps.Concelier.Documents; using StellaOps.Concelier.Models; using StellaOps.Concelier.Storage; namespace StellaOps.Tools.FixtureUpdater; public sealed record FixtureUpdaterOptions( string? RepoRoot, string OsvFixturesPath, string GhsaFixturesPath, string NvdFixturesPath, DateTimeOffset FixedTime); public readonly record struct FixtureUpdateResult(int ErrorCount); public sealed class FixtureUpdaterRunner { private readonly FixtureUpdaterOptions _options; private readonly Action _info; private readonly Action _error; private readonly FixtureDeterminism _determinism; private readonly JsonSerializerOptions _serializerOptions; private int _errors; public FixtureUpdaterRunner(FixtureUpdaterOptions options, Action? info = null, Action? error = null) { _options = options; _info = info ?? (_ => { }); _error = error ?? (_ => { }); _determinism = new FixtureDeterminism(options.FixedTime); _serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web) { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; } public FixtureUpdateResult Run() { _errors = 0; Directory.CreateDirectory(_options.OsvFixturesPath); Directory.CreateDirectory(_options.GhsaFixturesPath); Directory.CreateDirectory(_options.NvdFixturesPath); RewriteOsvFixtures(_options.OsvFixturesPath); RewriteSnapshotFixtures(_options.OsvFixturesPath); RewriteGhsaFixtures(_options.GhsaFixturesPath); RewriteCreditParityFixtures(_options.GhsaFixturesPath, _options.NvdFixturesPath); return new FixtureUpdateResult(_errors); } private void RewriteOsvFixtures(string fixturesPath) { var rawPath = Path.Combine(fixturesPath, "osv-ghsa.raw-osv.json"); if (!File.Exists(rawPath)) { ReportError($"[FixtureUpdater] OSV raw fixture missing: {rawPath}"); return; } JsonDocument document; try { document = JsonDocument.Parse(File.ReadAllText(rawPath)); } catch (JsonException ex) { ReportError($"[FixtureUpdater] Failed to parse OSV raw fixture '{rawPath}': {ex.Message}"); return; } using (document) { if (document.RootElement.ValueKind != JsonValueKind.Array) { ReportError($"[FixtureUpdater] OSV raw fixture '{rawPath}' is not a JSON array."); return; } var advisories = new List(); var index = 0; foreach (var element in document.RootElement.EnumerateArray()) { index++; OsvVulnerabilityDto? dto; try { dto = JsonSerializer.Deserialize(element.GetRawText(), _serializerOptions); } catch (JsonException ex) { ReportError($"[FixtureUpdater] OSV entry {index} parse failed in '{rawPath}': {ex.Message}"); continue; } if (dto is null) { ReportError($"[FixtureUpdater] OSV entry {index} was empty in '{rawPath}'."); continue; } var identifier = dto.Id ?? $"osv-entry-{index}"; var ecosystem = dto.Affected?.FirstOrDefault()?.Package?.Ecosystem ?? "unknown"; var capturedAt = dto.Modified ?? dto.Published ?? _determinism.UtcNow; var uri = new Uri($"https://osv.dev/vulnerability/{identifier}"); var documentRecord = new DocumentRecord( _determinism.CreateGuid("osv-document", identifier), OsvConnectorPlugin.SourceName, uri.ToString(), capturedAt, "fixture-sha", DocumentStatuses.PendingMap, "application/json", null, new Dictionary(StringComparer.Ordinal) { ["osv.ecosystem"] = ecosystem, }, null, capturedAt, null, null); var payload = DocumentObject.Parse(element.GetRawText()); var dtoRecord = new DtoRecord( _determinism.CreateGuid("osv-dto", identifier), documentRecord.Id, OsvConnectorPlugin.SourceName, "osv.v1", payload, capturedAt); var advisory = OsvMapper.Map(dto, documentRecord, dtoRecord, ecosystem); advisories.Add(advisory); } advisories.Sort((left, right) => string.Compare(left.AdvisoryKey, right.AdvisoryKey, StringComparison.Ordinal)); var snapshot = SnapshotSerializer.ToSnapshot(advisories); var outputPath = Path.Combine(fixturesPath, "osv-ghsa.osv.json"); File.WriteAllText(outputPath, snapshot); _info($"[FixtureUpdater] Updated {outputPath}"); } } private void RewriteSnapshotFixtures(string fixturesPath) { var baselinePublished = new DateTimeOffset(2025, 1, 5, 12, 0, 0, TimeSpan.Zero); var baselineModified = new DateTimeOffset(2025, 1, 8, 6, 30, 0, TimeSpan.Zero); var baselineFetched = new DateTimeOffset(2025, 1, 8, 7, 0, 0, TimeSpan.Zero); var cases = new (string Ecosystem, string Purl, string PackageName, string SnapshotFile)[] { ("npm", "pkg:npm/%40scope%2Fleft-pad", "@scope/left-pad", "osv-npm.snapshot.json"), ("PyPI", "pkg:pypi/requests", "requests", "osv-pypi.snapshot.json"), }; foreach (var (ecosystem, purl, packageName, snapshotFile) in cases) { var dto = new OsvVulnerabilityDto { Id = $"OSV-2025-{ecosystem}-0001", Summary = $"{ecosystem} package vulnerability", Details = $"Detailed description for {ecosystem} package {packageName}.", Published = baselinePublished, Modified = baselineModified, Aliases = new[] { $"CVE-2025-11{ecosystem.Length}", $"GHSA-{ecosystem.Length}abc-{ecosystem.Length}def-{ecosystem.Length}ghi" }, Related = new[] { $"OSV-RELATED-{ecosystem}-42" }, References = new[] { new OsvReferenceDto { Url = $"https://example.com/{ecosystem}/advisory", Type = "ADVISORY" }, new OsvReferenceDto { Url = $"https://example.com/{ecosystem}/fix", Type = "FIX" }, }, Severity = new[] { new OsvSeverityDto { Type = "CVSS_V3", Score = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" }, }, Affected = new[] { new OsvAffectedPackageDto { Package = new OsvPackageDto { Ecosystem = ecosystem, Name = packageName, Purl = purl, }, Ranges = new[] { new OsvRangeDto { Type = "SEMVER", Events = new[] { new OsvEventDto { Introduced = "0" }, new OsvEventDto { Fixed = "2.0.0" }, }, }, }, Versions = new[] { "1.0.0", "1.5.0" }, EcosystemSpecific = JsonDocument.Parse("{\"severity\":\"high\"}").RootElement.Clone(), }, }, DatabaseSpecific = JsonDocument.Parse("{\"source\":\"osv.dev\"}").RootElement.Clone(), }; var identifier = dto.Id ?? $"snapshot-{ecosystem}"; var document = new DocumentRecord( _determinism.CreateGuid("osv-snapshot-document", identifier), OsvConnectorPlugin.SourceName, $"https://osv.dev/vulnerability/{dto.Id}", baselineFetched, "fixture-sha", DocumentStatuses.PendingParse, "application/json", null, new Dictionary(StringComparer.Ordinal) { ["osv.ecosystem"] = ecosystem }, null, baselineModified, null); var payload = DocumentObject.Parse(JsonSerializer.Serialize(dto, _serializerOptions)); var dtoRecord = new DtoRecord( _determinism.CreateGuid("osv-snapshot-dto", identifier), document.Id, OsvConnectorPlugin.SourceName, "osv.v1", payload, baselineModified); var advisory = OsvMapper.Map(dto, document, dtoRecord, ecosystem); var snapshot = SnapshotSerializer.ToSnapshot(advisory); var outputPath = Path.Combine(fixturesPath, snapshotFile); File.WriteAllText(outputPath, snapshot); _info($"[FixtureUpdater] Updated {outputPath}"); } } private void RewriteGhsaFixtures(string fixturesPath) { var rawPath = Path.Combine(fixturesPath, "osv-ghsa.raw-ghsa.json"); if (!File.Exists(rawPath)) { ReportError($"[FixtureUpdater] GHSA raw fixture missing: {rawPath}"); return; } JsonDocument document; try { document = JsonDocument.Parse(File.ReadAllText(rawPath)); } catch (JsonException ex) { ReportError($"[FixtureUpdater] Failed to parse GHSA raw fixture '{rawPath}': {ex.Message}"); return; } using (document) { if (document.RootElement.ValueKind != JsonValueKind.Array) { ReportError($"[FixtureUpdater] GHSA raw fixture '{rawPath}' is not a JSON array."); return; } var advisories = new List(); var index = 0; foreach (var element in document.RootElement.EnumerateArray()) { index++; GhsaRecordDto dto; try { dto = GhsaRecordParser.Parse(Encoding.UTF8.GetBytes(element.GetRawText())); } catch (JsonException ex) { ReportError($"[FixtureUpdater] GHSA entry {index} parse failed in '{rawPath}': {ex.Message}"); continue; } var identifier = string.IsNullOrWhiteSpace(dto.GhsaId) ? $"ghsa-entry-{index}" : dto.GhsaId; var capturedAt = _determinism.UtcNow; var uri = new Uri($"https://github.com/advisories/{identifier}"); var documentRecord = new DocumentRecord( _determinism.CreateGuid("ghsa-document", identifier), GhsaConnectorPlugin.SourceName, uri.ToString(), capturedAt, "fixture-sha", DocumentStatuses.PendingMap, "application/json", null, new Dictionary(StringComparer.Ordinal), null, capturedAt, null, null); var advisory = GhsaMapper.Map(dto, documentRecord, capturedAt); advisories.Add(advisory); } advisories.Sort((left, right) => string.Compare(left.AdvisoryKey, right.AdvisoryKey, StringComparison.Ordinal)); var snapshot = SnapshotSerializer.ToSnapshot(advisories); var outputPath = Path.Combine(fixturesPath, "osv-ghsa.ghsa.json"); File.WriteAllText(outputPath, snapshot); _info($"[FixtureUpdater] Updated {outputPath}"); } } private void RewriteCreditParityFixtures(string ghsaFixturesPath, string nvdFixturesPath) { Directory.CreateDirectory(ghsaFixturesPath); Directory.CreateDirectory(nvdFixturesPath); var advisoryKeyGhsa = "GHSA-credit-parity"; var advisoryKeyNvd = "CVE-2025-5555"; var recordedAt = new DateTimeOffset(2025, 10, 10, 15, 0, 0, TimeSpan.Zero); var published = new DateTimeOffset(2025, 10, 9, 18, 30, 0, TimeSpan.Zero); var modified = new DateTimeOffset(2025, 10, 10, 12, 0, 0, TimeSpan.Zero); AdvisoryCredit[] CreateCredits(string source) => [ CreateCredit("Alice Researcher", "reporter", new[] { "mailto:alice.researcher@example.com" }, source), CreateCredit("Bob Maintainer", "remediation_developer", new[] { "https://github.com/acme/bob-maintainer" }, source) ]; AdvisoryCredit CreateCredit(string displayName, string role, IReadOnlyList contacts, string source) { var provenance = new AdvisoryProvenance( source, "credit", $"{source}:{displayName.ToLowerInvariant().Replace(' ', '-')}", recordedAt, new[] { ProvenanceFieldMasks.Credits }); return new AdvisoryCredit(displayName, role, contacts, provenance); } AdvisoryReference[] CreateReferences(string sourceName, params (string Url, string Kind)[] entries) { if (entries is null || entries.Length == 0) { return Array.Empty(); } var references = new List(entries.Length); foreach (var entry in entries) { var provenance = new AdvisoryProvenance( sourceName, "reference", entry.Url, recordedAt, new[] { ProvenanceFieldMasks.References }); references.Add(new AdvisoryReference( entry.Url, entry.Kind, sourceTag: null, summary: null, provenance)); } return references.ToArray(); } Advisory CreateAdvisory( string sourceName, string advisoryKey, IEnumerable aliases, AdvisoryCredit[] credits, AdvisoryReference[] references, string documentValue) { var documentProvenance = new AdvisoryProvenance( sourceName, "document", documentValue, recordedAt, new[] { ProvenanceFieldMasks.Advisory }); var mappingProvenance = new AdvisoryProvenance( sourceName, "mapping", advisoryKey, recordedAt, new[] { ProvenanceFieldMasks.Advisory }); return new Advisory( advisoryKey, "Credit parity regression fixture", "Credit parity regression fixture", "en", published, modified, "moderate", exploitKnown: false, aliases, credits, references, Array.Empty(), Array.Empty(), new[] { documentProvenance, mappingProvenance }); } var ghsa = CreateAdvisory( "ghsa", advisoryKeyGhsa, new[] { advisoryKeyGhsa, advisoryKeyNvd }, CreateCredits("ghsa"), CreateReferences( "ghsa", ($"https://github.com/advisories/{advisoryKeyGhsa}", "advisory"), ("https://example.com/ghsa/patch", "patch")), $"security/advisories/{advisoryKeyGhsa}"); var osv = CreateAdvisory( OsvConnectorPlugin.SourceName, advisoryKeyGhsa, new[] { advisoryKeyGhsa, advisoryKeyNvd }, CreateCredits(OsvConnectorPlugin.SourceName), CreateReferences( OsvConnectorPlugin.SourceName, ($"https://github.com/advisories/{advisoryKeyGhsa}", "advisory"), ($"https://osv.dev/vulnerability/{advisoryKeyGhsa}", "advisory")), $"https://osv.dev/vulnerability/{advisoryKeyGhsa}"); var nvd = CreateAdvisory( NvdConnectorPlugin.SourceName, advisoryKeyNvd, new[] { advisoryKeyNvd, advisoryKeyGhsa }, CreateCredits(NvdConnectorPlugin.SourceName), CreateReferences( NvdConnectorPlugin.SourceName, ($"https://services.nvd.nist.gov/vuln/detail/{advisoryKeyNvd}", "advisory"), ("https://example.com/nvd/reference", "report")), $"https://services.nvd.nist.gov/vuln/detail/{advisoryKeyNvd}"); var ghsaSnapshot = SnapshotSerializer.ToSnapshot(ghsa); var osvSnapshot = SnapshotSerializer.ToSnapshot(osv); var nvdSnapshot = SnapshotSerializer.ToSnapshot(nvd); File.WriteAllText(Path.Combine(ghsaFixturesPath, "credit-parity.ghsa.json"), ghsaSnapshot); File.WriteAllText(Path.Combine(ghsaFixturesPath, "credit-parity.osv.json"), osvSnapshot); File.WriteAllText(Path.Combine(ghsaFixturesPath, "credit-parity.nvd.json"), nvdSnapshot); File.WriteAllText(Path.Combine(nvdFixturesPath, "credit-parity.ghsa.json"), ghsaSnapshot); File.WriteAllText(Path.Combine(nvdFixturesPath, "credit-parity.osv.json"), osvSnapshot); File.WriteAllText(Path.Combine(nvdFixturesPath, "credit-parity.nvd.json"), nvdSnapshot); _info($"[FixtureUpdater] Updated credit parity fixtures under {ghsaFixturesPath} and {nvdFixturesPath}"); } private void ReportError(string message) { _errors++; _error(message); } } internal sealed class FixtureDeterminism { private readonly DateTimeOffset _fixedTime; public FixtureDeterminism(DateTimeOffset fixedTime) { _fixedTime = fixedTime; } public DateTimeOffset UtcNow => _fixedTime; public Guid CreateGuid(string scope, string key) => CreateDeterministicGuid($"{scope}:{key}"); private static Guid CreateDeterministicGuid(string value) { using var sha = SHA256.Create(); var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(value)); Span bytes = stackalloc byte[16]; hash.AsSpan(0, 16).CopyTo(bytes); bytes[6] = (byte)((bytes[6] & 0x0F) | 0x50); bytes[8] = (byte)((bytes[8] & 0x3F) | 0x80); return new Guid(bytes); } } internal static class RepoRootLocator { public static string? TryResolve(string? repoRoot) { if (!string.IsNullOrWhiteSpace(repoRoot)) { return Path.GetFullPath(repoRoot); } var current = new DirectoryInfo(Directory.GetCurrentDirectory()); while (current is not null) { var solutionPath = Path.Combine(current.FullName, "src", "StellaOps.sln"); if (File.Exists(solutionPath)) { return current.FullName; } current = current.Parent; } return null; } } internal static class FixtureUpdaterDefaults { public static readonly DateTimeOffset DefaultFixedTime = new(2025, 1, 5, 0, 0, 0, TimeSpan.Zero); public static readonly string OsvFixturesRelative = Path.Combine("src", "Concelier", "__Tests", "StellaOps.Concelier.Connector.Osv.Tests", "Fixtures"); public static readonly string GhsaFixturesRelative = Path.Combine("src", "Concelier", "__Tests", "StellaOps.Concelier.Connector.Ghsa.Tests", "Fixtures"); public static readonly string NvdFixturesRelative = Path.Combine("src", "Concelier", "__Tests", "StellaOps.Concelier.Connector.Nvd.Tests", "Nvd", "Fixtures"); }